From 2f8fb0a0a1493b19a84a8d4fcb863ddfc2f66db8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 15:20:51 +0000 Subject: [PATCH 01/19] feat(api): aggregated API specs update --- .stats.yml | 6 +- src/gcore/resources/dns/api.md | 1 - src/gcore/resources/dns/network_mappings.py | 96 ------------------- src/gcore/types/dns/zones/dns_output_rrset.py | 14 +-- .../types/dns/zones/rrset_create_params.py | 14 +-- .../types/dns/zones/rrset_replace_params.py | 14 +-- .../dns/test_network_mappings.py | 82 ---------------- 7 files changed, 18 insertions(+), 209 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0b6c3449..26728fb6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e7708c017357421a39e6de2f7141415c93951bbc835742909b0d9d6f0825a318.yml -openapi_spec_hash: 2abb653b57137b808c182cb1e778a429 -config_hash: ab6b5443d52ca04e4e0e12def131f8e6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e8141952a77a5faabf98feca4f964baaef5b84aa66d785c8b35357e48928df9c.yml +openapi_spec_hash: a5ff6021de586dafea9df886214ceb2c +config_hash: 0adfdd90fff02d0c623f304ff15a85ae diff --git a/src/gcore/resources/dns/api.md b/src/gcore/resources/dns/api.md index ff89a8bc..63494906 100644 --- a/src/gcore/resources/dns/api.md +++ b/src/gcore/resources/dns/api.md @@ -154,6 +154,5 @@ Methods: - client.dns.network_mappings.list(\*\*params) -> NetworkMappingListResponse - client.dns.network_mappings.delete(id) -> object - client.dns.network_mappings.get(id) -> DNSNetworkMapping -- client.dns.network_mappings.get_by_name(name) -> DNSNetworkMapping - client.dns.network*mappings.import*() -> NetworkMappingImportResponse - client.dns.network_mappings.replace(id, \*\*params) -> object diff --git a/src/gcore/resources/dns/network_mappings.py b/src/gcore/resources/dns/network_mappings.py index 567435c6..ffd44c7a 100644 --- a/src/gcore/resources/dns/network_mappings.py +++ b/src/gcore/resources/dns/network_mappings.py @@ -263,48 +263,6 @@ def get( cast_to=DNSNetworkMapping, ) - def get_by_name( - self, - name: 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, - ) -> DNSNetworkMapping: - """ - Get network mapping by name. - - Particular network mapping item info - - Example of request: - - ``` - curl --location --request GET 'https://api.gcore.com/dns/v2/network-mappings/test-mapping' \\ - --header 'Authorization: Bearer ...' - ``` - - Args: - 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 not name: - raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") - return self._get( - path_template("/dns/v2/network-mappings/{name}", name=name), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DNSNetworkMapping, - ) - def import_( self, *, @@ -697,48 +655,6 @@ async def get( cast_to=DNSNetworkMapping, ) - async def get_by_name( - self, - name: 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, - ) -> DNSNetworkMapping: - """ - Get network mapping by name. - - Particular network mapping item info - - Example of request: - - ``` - curl --location --request GET 'https://api.gcore.com/dns/v2/network-mappings/test-mapping' \\ - --header 'Authorization: Bearer ...' - ``` - - Args: - 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 not name: - raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") - return await self._get( - path_template("/dns/v2/network-mappings/{name}", name=name), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DNSNetworkMapping, - ) - async def import_( self, *, @@ -912,9 +828,6 @@ def __init__(self, network_mappings: NetworkMappingsResource) -> None: self.get = to_raw_response_wrapper( network_mappings.get, ) - self.get_by_name = to_raw_response_wrapper( - network_mappings.get_by_name, - ) self.import_ = to_raw_response_wrapper( network_mappings.import_, ) @@ -939,9 +852,6 @@ def __init__(self, network_mappings: AsyncNetworkMappingsResource) -> None: self.get = async_to_raw_response_wrapper( network_mappings.get, ) - self.get_by_name = async_to_raw_response_wrapper( - network_mappings.get_by_name, - ) self.import_ = async_to_raw_response_wrapper( network_mappings.import_, ) @@ -966,9 +876,6 @@ def __init__(self, network_mappings: NetworkMappingsResource) -> None: self.get = to_streamed_response_wrapper( network_mappings.get, ) - self.get_by_name = to_streamed_response_wrapper( - network_mappings.get_by_name, - ) self.import_ = to_streamed_response_wrapper( network_mappings.import_, ) @@ -993,9 +900,6 @@ def __init__(self, network_mappings: AsyncNetworkMappingsResource) -> None: self.get = async_to_streamed_response_wrapper( network_mappings.get, ) - self.get_by_name = async_to_streamed_response_wrapper( - network_mappings.get_by_name, - ) self.import_ = async_to_streamed_response_wrapper( network_mappings.import_, ) diff --git a/src/gcore/types/dns/zones/dns_output_rrset.py b/src/gcore/types/dns/zones/dns_output_rrset.py index 67b794d6..169ecc4c 100644 --- a/src/gcore/types/dns/zones/dns_output_rrset.py +++ b/src/gcore/types/dns/zones/dns_output_rrset.py @@ -14,15 +14,11 @@ class ResourceRecord(BaseModel): """ Content of resource record The exact length of the array depends on the type of rrset, each individual record parameter must be a separate element of the array. - For example - - - SRV-record: `[100, 1, 5061, "example.com"]` - - CNAME-record: `[ "the.target.domain" ]` - - A-record: `[ "1.2.3.4", "5.6.7.8" ]` - - AAAA-record: `[ "2001:db8::1", "2001:db8::2" ]` - - MX-record: `[ "mail1.example.com", "mail2.example.com" ]` - - SVCB/HTTPS-record: - `[ 1, ".", ["alpn", "h3", "h2"], [ "port", 1443 ], [ "ipv4hint", "10.0.0.1" ], [ "ech", "AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA" ] ]` + For example SRV-record: `[100, 1, 5061, "example.com"]` CNAME-record: + `[ "the.target.domain" ]` A-record: `[ "1.2.3.4", "5.6.7.8" ]` AAAA-record: + `[ "2001:db8::1", "2001:db8::2" ]` MX-record: + `[ "mail1.example.com", "mail2.example.com" ]` SVCB/HTTPS-record: + `[ 1, ".", ["alpn", "h3", "h2"], [ "port", 1443 ], [ "ipv4hint", "10.0.0.1" ], [ "ech", "AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA" ] ]` """ id: Optional[int] = None diff --git a/src/gcore/types/dns/zones/rrset_create_params.py b/src/gcore/types/dns/zones/rrset_create_params.py index e4b2f876..a5bfdef1 100644 --- a/src/gcore/types/dns/zones/rrset_create_params.py +++ b/src/gcore/types/dns/zones/rrset_create_params.py @@ -34,15 +34,11 @@ class ResourceRecord(TypedDict, total=False): """ Content of resource record The exact length of the array depends on the type of rrset, each individual record parameter must be a separate element of the array. - For example - - - SRV-record: `[100, 1, 5061, "example.com"]` - - CNAME-record: `[ "the.target.domain" ]` - - A-record: `[ "1.2.3.4", "5.6.7.8" ]` - - AAAA-record: `[ "2001:db8::1", "2001:db8::2" ]` - - MX-record: `[ "mail1.example.com", "mail2.example.com" ]` - - SVCB/HTTPS-record: - `[ 1, ".", ["alpn", "h3", "h2"], [ "port", 1443 ], [ "ipv4hint", "10.0.0.1" ], [ "ech", "AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA" ] ]` + For example SRV-record: `[100, 1, 5061, "example.com"]` CNAME-record: + `[ "the.target.domain" ]` A-record: `[ "1.2.3.4", "5.6.7.8" ]` AAAA-record: + `[ "2001:db8::1", "2001:db8::2" ]` MX-record: + `[ "mail1.example.com", "mail2.example.com" ]` SVCB/HTTPS-record: + `[ 1, ".", ["alpn", "h3", "h2"], [ "port", 1443 ], [ "ipv4hint", "10.0.0.1" ], [ "ech", "AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA" ] ]` """ enabled: bool diff --git a/src/gcore/types/dns/zones/rrset_replace_params.py b/src/gcore/types/dns/zones/rrset_replace_params.py index 46f6274c..b2d155ec 100644 --- a/src/gcore/types/dns/zones/rrset_replace_params.py +++ b/src/gcore/types/dns/zones/rrset_replace_params.py @@ -34,15 +34,11 @@ class ResourceRecord(TypedDict, total=False): """ Content of resource record The exact length of the array depends on the type of rrset, each individual record parameter must be a separate element of the array. - For example - - - SRV-record: `[100, 1, 5061, "example.com"]` - - CNAME-record: `[ "the.target.domain" ]` - - A-record: `[ "1.2.3.4", "5.6.7.8" ]` - - AAAA-record: `[ "2001:db8::1", "2001:db8::2" ]` - - MX-record: `[ "mail1.example.com", "mail2.example.com" ]` - - SVCB/HTTPS-record: - `[ 1, ".", ["alpn", "h3", "h2"], [ "port", 1443 ], [ "ipv4hint", "10.0.0.1" ], [ "ech", "AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA" ] ]` + For example SRV-record: `[100, 1, 5061, "example.com"]` CNAME-record: + `[ "the.target.domain" ]` A-record: `[ "1.2.3.4", "5.6.7.8" ]` AAAA-record: + `[ "2001:db8::1", "2001:db8::2" ]` MX-record: + `[ "mail1.example.com", "mail2.example.com" ]` SVCB/HTTPS-record: + `[ 1, ".", ["alpn", "h3", "h2"], [ "port", 1443 ], [ "ipv4hint", "10.0.0.1" ], [ "ech", "AEn+DQBFKwAgACABWIHUGj4u+PIggYXcR5JF0gYk3dCRioBW8uJq9H4mKAAIAAEAAQABAANAEnB1YmxpYy50bHMtZWNoLmRldgAA" ] ]` """ enabled: bool diff --git a/tests/api_resources/dns/test_network_mappings.py b/tests/api_resources/dns/test_network_mappings.py index 2211c8fe..90c3706f 100644 --- a/tests/api_resources/dns/test_network_mappings.py +++ b/tests/api_resources/dns/test_network_mappings.py @@ -158,47 +158,6 @@ def test_streaming_response_get(self, client: Gcore) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="DNS-2948: OpenAPI spec has ambiguous overlapping path templates {id} vs {name}") - @parametrize - def test_method_get_by_name(self, client: Gcore) -> None: - network_mapping = client.dns.network_mappings.get_by_name( - "name", - ) - assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) - - @pytest.mark.skip(reason="DNS-2948: OpenAPI spec has ambiguous overlapping path templates {id} vs {name}") - @parametrize - def test_raw_response_get_by_name(self, client: Gcore) -> None: - response = client.dns.network_mappings.with_raw_response.get_by_name( - "name", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - network_mapping = response.parse() - assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) - - @pytest.mark.skip(reason="DNS-2948: OpenAPI spec has ambiguous overlapping path templates {id} vs {name}") - @parametrize - def test_streaming_response_get_by_name(self, client: Gcore) -> None: - with client.dns.network_mappings.with_streaming_response.get_by_name( - "name", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - network_mapping = response.parse() - assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_get_by_name(self, client: Gcore) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"): - client.dns.network_mappings.with_raw_response.get_by_name( - "", - ) - @parametrize def test_method_import(self, client: Gcore) -> None: network_mapping = client.dns.network_mappings.import_() @@ -412,47 +371,6 @@ async def test_streaming_response_get(self, async_client: AsyncGcore) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip(reason="DNS-2948: OpenAPI spec has ambiguous overlapping path templates {id} vs {name}") - @parametrize - async def test_method_get_by_name(self, async_client: AsyncGcore) -> None: - network_mapping = await async_client.dns.network_mappings.get_by_name( - "name", - ) - assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) - - @pytest.mark.skip(reason="DNS-2948: OpenAPI spec has ambiguous overlapping path templates {id} vs {name}") - @parametrize - async def test_raw_response_get_by_name(self, async_client: AsyncGcore) -> None: - response = await async_client.dns.network_mappings.with_raw_response.get_by_name( - "name", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - network_mapping = await response.parse() - assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) - - @pytest.mark.skip(reason="DNS-2948: OpenAPI spec has ambiguous overlapping path templates {id} vs {name}") - @parametrize - async def test_streaming_response_get_by_name(self, async_client: AsyncGcore) -> None: - async with async_client.dns.network_mappings.with_streaming_response.get_by_name( - "name", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - network_mapping = await response.parse() - assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_get_by_name(self, async_client: AsyncGcore) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"): - await async_client.dns.network_mappings.with_raw_response.get_by_name( - "", - ) - @parametrize async def test_method_import(self, async_client: AsyncGcore) -> None: network_mapping = await async_client.dns.network_mappings.import_() From 7beaf49ae0cd289d6a60a79de18ed722a9cbf088 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:28:38 +0000 Subject: [PATCH 02/19] feat(api): aggregated API specs update --- .stats.yml | 8 +- src/gcore/resources/cloud/api.md | 8 +- src/gcore/resources/cloud/baremetal/images.py | 30 ++-- src/gcore/resources/cloud/instances/images.py | 28 +++- src/gcore/types/cloud/baremetal/__init__.py | 1 + .../cloud/baremetal/image_list_params.py | 8 +- .../cloud/baremetal/image_list_response.py | 129 ++++++++++++++++++ .../cloud/baremetal/server_create_params.py | 5 +- src/gcore/types/cloud/billing_reservation.py | 2 +- .../types/cloud/cost_report_aggregated.py | 2 +- .../cloud/cost_report_aggregated_monthly.py | 2 +- src/gcore/types/cloud/cost_report_detailed.py | 2 +- src/gcore/types/cloud/ddos_profile_field.py | 10 +- .../clusters/interface_attach_params.py | 28 ++-- src/gcore/types/cloud/image.py | 90 ++++++------ .../image_create_from_volume_params.py | 2 + .../cloud/instances/image_list_params.py | 8 +- .../instances/interface_attach_params.py | 28 ++-- .../types/cloud/k8s/cluster_create_params.py | 5 +- .../types/cloud/k8s/cluster_update_params.py | 5 +- src/gcore/types/cloud/k8s/k8s_cluster.py | 5 +- src/gcore/types/cloud/region.py | 3 + src/gcore/types/cloud/usage_report.py | 4 +- .../cloud/baremetal/test_images.py | 54 ++++---- .../cloud/baremetal/test_servers.py | 2 - .../gpu_baremetal/clusters/test_interfaces.py | 24 ++-- .../cloud/instances/test_images.py | 68 ++++----- .../cloud/instances/test_interfaces.py | 24 ++-- .../api_resources/cloud/k8s/test_clusters.py | 4 - 29 files changed, 353 insertions(+), 236 deletions(-) create mode 100644 src/gcore/types/cloud/baremetal/image_list_response.py diff --git a/.stats.yml b/.stats.yml index 26728fb6..4404c997 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e8141952a77a5faabf98feca4f964baaef5b84aa66d785c8b35357e48928df9c.yml -openapi_spec_hash: a5ff6021de586dafea9df886214ceb2c -config_hash: 0adfdd90fff02d0c623f304ff15a85ae +configured_endpoints: 657 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-c30b070f2f0ce73f80dfb6c950d3e46672acaedef0a7f011e75065477779bdf6.yml +openapi_spec_hash: 56a6b5ca8f97983419b03f5e33b36594 +config_hash: ab6b5443d52ca04e4e0e12def131f8e6 diff --git a/src/gcore/resources/cloud/api.md b/src/gcore/resources/cloud/api.md index fb309d36..40c43971 100644 --- a/src/gcore/resources/cloud/api.md +++ b/src/gcore/resources/cloud/api.md @@ -646,9 +646,15 @@ Methods: ### Images +Types: + +```python +from gcore.types.cloud.baremetal import ImageListResponse +``` + Methods: -- client.cloud.baremetal.images.list(\*, project_id, region_id, \*\*params) -> ImageList +- client.cloud.baremetal.images.list(\*, project_id, region_id, \*\*params) -> ImageListResponse ### Flavors diff --git a/src/gcore/resources/cloud/baremetal/images.py b/src/gcore/resources/cloud/baremetal/images.py index cbd700b5..f0bac9bc 100644 --- a/src/gcore/resources/cloud/baremetal/images.py +++ b/src/gcore/resources/cloud/baremetal/images.py @@ -18,7 +18,7 @@ ) from ...._base_client import make_request_options from ....types.cloud.baremetal import image_list_params -from ....types.cloud.image_list import ImageList +from ....types.cloud.baremetal.image_list_response import ImageListResponse __all__ = ["ImagesResource", "AsyncImagesResource"] @@ -59,7 +59,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ImageList: + ) -> ImageListResponse: """Retrieve a list of available images for bare metal servers. The list can be @@ -67,13 +67,17 @@ def list( not be owned by the project. Args: - include_prices: Show price + project_id: Project ID + + region_id: Region ID + + include_prices: Show price. private: Any value to show private images - tag_key: Filter by tag keys. + tag_key: Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2 - tag_key_value: Filter by tag key-value pairs. Must be a valid JSON string. + tag_key_value: Optional. Filter by tag key-value pairs. visibility: Image visibility. Globally visible images are public @@ -107,7 +111,7 @@ def list( image_list_params.ImageListParams, ), ), - cast_to=ImageList, + cast_to=ImageListResponse, ) @@ -147,7 +151,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ImageList: + ) -> ImageListResponse: """Retrieve a list of available images for bare metal servers. The list can be @@ -155,13 +159,17 @@ async def list( not be owned by the project. Args: - include_prices: Show price + project_id: Project ID + + region_id: Region ID + + include_prices: Show price. private: Any value to show private images - tag_key: Filter by tag keys. + tag_key: Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2 - tag_key_value: Filter by tag key-value pairs. Must be a valid JSON string. + tag_key_value: Optional. Filter by tag key-value pairs. visibility: Image visibility. Globally visible images are public @@ -195,7 +203,7 @@ async def list( image_list_params.ImageListParams, ), ), - cast_to=ImageList, + cast_to=ImageListResponse, ) diff --git a/src/gcore/resources/cloud/instances/images.py b/src/gcore/resources/cloud/instances/images.py index 6599d48b..8fe2f588 100644 --- a/src/gcore/resources/cloud/instances/images.py +++ b/src/gcore/resources/cloud/instances/images.py @@ -163,13 +163,17 @@ def list( by the project or are public/shared with the client. Args: - include_prices: Show price + project_id: Project ID + + region_id: Region ID + + include_prices: Show price. private: Any value to show private images - tag_key: Filter by tag keys. + tag_key: Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2 - tag_key_value: Filter by tag key-value pairs. Must be a valid JSON string. + tag_key_value: Optional. Filter by tag key-value pairs. visibility: Image visibility. Globally visible images are public @@ -316,6 +320,10 @@ def create_from_volume( an image from it. Args: + project_id: Project ID + + region_id: Region ID + name: Image name volume_id: Required if source is volume. Volume id @@ -785,13 +793,17 @@ async def list( by the project or are public/shared with the client. Args: - include_prices: Show price + project_id: Project ID + + region_id: Region ID + + include_prices: Show price. private: Any value to show private images - tag_key: Filter by tag keys. + tag_key: Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2 - tag_key_value: Filter by tag key-value pairs. Must be a valid JSON string. + tag_key_value: Optional. Filter by tag key-value pairs. visibility: Image visibility. Globally visible images are public @@ -938,6 +950,10 @@ async def create_from_volume( an image from it. Args: + project_id: Project ID + + region_id: Region ID + name: Image name volume_id: Required if source is volume. Volume id diff --git a/src/gcore/types/cloud/baremetal/__init__.py b/src/gcore/types/cloud/baremetal/__init__.py index c96b5497..7bef20d4 100644 --- a/src/gcore/types/cloud/baremetal/__init__.py +++ b/src/gcore/types/cloud/baremetal/__init__.py @@ -6,6 +6,7 @@ from .image_list_params import ImageListParams as ImageListParams from .flavor_list_params import FlavorListParams as FlavorListParams from .server_list_params import ServerListParams as ServerListParams +from .image_list_response import ImageListResponse as ImageListResponse from .server_create_params import ServerCreateParams as ServerCreateParams from .server_delete_params import ServerDeleteParams as ServerDeleteParams from .server_update_params import ServerUpdateParams as ServerUpdateParams diff --git a/src/gcore/types/cloud/baremetal/image_list_params.py b/src/gcore/types/cloud/baremetal/image_list_params.py index 1e1d2903..db3e8854 100644 --- a/src/gcore/types/cloud/baremetal/image_list_params.py +++ b/src/gcore/types/cloud/baremetal/image_list_params.py @@ -11,20 +11,22 @@ class ImageListParams(TypedDict, total=False): project_id: int + """Project ID""" region_id: int + """Region ID""" include_prices: bool - """Show price""" + """Show price.""" private: str """Any value to show private images""" tag_key: SequenceNotStr[str] - """Filter by tag keys.""" + """Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2""" tag_key_value: str - """Filter by tag key-value pairs. Must be a valid JSON string.""" + """Optional. Filter by tag key-value pairs.""" visibility: Literal["private", "public", "shared"] """Image visibility. Globally visible images are public""" diff --git a/src/gcore/types/cloud/baremetal/image_list_response.py b/src/gcore/types/cloud/baremetal/image_list_response.py new file mode 100644 index 00000000..81c15f77 --- /dev/null +++ b/src/gcore/types/cloud/baremetal/image_list_response.py @@ -0,0 +1,129 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime +from typing_extensions import Literal + +from ..tag import Tag +from ...._models import BaseModel + +__all__ = ["ImageListResponse", "Result"] + + +class Result(BaseModel): + id: str + """Image ID""" + + architecture: Literal["aarch64", "x86_64"] + """An image architecture type: aarch64, `x86_64`.""" + + created_at: datetime + """Datetime when the image was created""" + + creator_task_id: Optional[str] = None + """Task that created this entity""" + + currency_code: Optional[str] = None + """Currency code. Shown if the `include_prices` query parameter if set to true""" + + description: Optional[str] = None + """Image description""" + + disk_format: str + """Disk format""" + + display_order: Optional[int] = None + + gpu_driver: Optional[str] = None + """Name of the GPU driver vendor""" + + gpu_driver_type: Optional[str] = None + """Type of the GPU driver""" + + gpu_driver_version: Optional[str] = None + """Version of the installed GPU driver""" + + hw_firmware_type: Optional[Literal["bios", "uefi"]] = None + """Specifies the type of firmware with which to boot the guest.""" + + hw_machine_type: Optional[Literal["pc", "q35"]] = None + """A virtual chipset type.""" + + is_baremetal: Optional[bool] = None + """For bare metal servers this value is always set to true""" + + min_disk: int + """Minimal boot volume required""" + + min_ram: int + """Minimal VM RAM required""" + + name: str + """Image display name""" + + os_distro: str + """OS Distribution, i.e. Debian, CentOS, Ubuntu, CoreOS etc.""" + + os_type: Literal["linux", "windows"] + """The operating system installed on the image.""" + + os_version: str + """OS version, i.e. 19.04 (for Ubuntu) or 9.4 for Debian""" + + price_per_hour: Optional[float] = None + """Price per hour. Shown if the `include_prices` query parameter if set to true""" + + price_per_month: Optional[float] = None + """Price per month. Shown if the `include_prices` query parameter if set to true""" + + price_status: Optional[Literal["error", "hide", "show"]] = None + """Price status for the UI""" + + project_id: int + """Project ID""" + + region: str + """Region name""" + + region_id: int + """Region ID""" + + size: Optional[int] = None + """Image size in bytes""" + + ssh_key: Optional[Literal["allow", "deny", "required"]] = None + """Whether the image supports SSH key or not""" + + status: str + """Image status, i.e. active""" + + tags: List[Tag] + """List of key-value tags associated with the resource. + + A tag is a key-value pair that can be associated with a resource, enabling + efficient filtering and grouping for better organization and management. Some + tags are read-only and cannot be modified by the user. Tags are also integrated + with cost reports, allowing cost data to be filtered based on tag keys or + values. + """ + + task_id: Optional[str] = None + """The UUID of the active task that currently holds a lock on the resource. + + This lock prevents concurrent modifications to ensure consistency. If `null`, + the resource is not locked. + """ + + updated_at: datetime + """Datetime when the image was updated""" + + visibility: str + """Image visibility. Globally visible images are public""" + + +class ImageListResponse(BaseModel): + count: int + """Number of objects""" + + results: List[Result] + """Objects""" diff --git a/src/gcore/types/cloud/baremetal/server_create_params.py b/src/gcore/types/cloud/baremetal/server_create_params.py index ad257adf..d406fbec 100644 --- a/src/gcore/types/cloud/baremetal/server_create_params.py +++ b/src/gcore/types/cloud/baremetal/server_create_params.py @@ -353,14 +353,11 @@ class InterfaceCreateBareMetalReservedFixedIPInterfaceSerializer(TypedDict, tota class DDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """Unique identifier of the DDoS protection field being configured""" field_value: object - value: Optional[str] - """Basic type value. Only one of 'value' or 'field_value' must be specified.""" - class DDOSProfile(TypedDict, total=False): """Enable advanced DDoS protection for the server""" diff --git a/src/gcore/types/cloud/billing_reservation.py b/src/gcore/types/cloud/billing_reservation.py index 9800c029..6b56ff23 100644 --- a/src/gcore/types/cloud/billing_reservation.py +++ b/src/gcore/types/cloud/billing_reservation.py @@ -48,7 +48,7 @@ class Commit(BaseModel): price_total: str """Total price for the reservation period for the full reserved amount""" - subscription_id: int + subscription_id: Optional[int] = None """Billing subscription ID for commit""" diff --git a/src/gcore/types/cloud/cost_report_aggregated.py b/src/gcore/types/cloud/cost_report_aggregated.py index 4697169e..a1fd1fb7 100644 --- a/src/gcore/types/cloud/cost_report_aggregated.py +++ b/src/gcore/types/cloud/cost_report_aggregated.py @@ -253,7 +253,7 @@ class ResultTotalEgressTrafficWithCostSerializer(BaseModel): err: Optional[str] = None """Error message""" - instance_type: Literal["baremetal", "vm"] + instance_type: Literal["baremetal", "router", "vm"] """Type of the instance""" region: int diff --git a/src/gcore/types/cloud/cost_report_aggregated_monthly.py b/src/gcore/types/cloud/cost_report_aggregated_monthly.py index d62de86d..4eb91aa4 100644 --- a/src/gcore/types/cloud/cost_report_aggregated_monthly.py +++ b/src/gcore/types/cloud/cost_report_aggregated_monthly.py @@ -253,7 +253,7 @@ class ResultTotalEgressTrafficWithCostSerializer(BaseModel): err: Optional[str] = None """Error message""" - instance_type: Literal["baremetal", "vm"] + instance_type: Literal["baremetal", "router", "vm"] """Type of the instance""" region: int diff --git a/src/gcore/types/cloud/cost_report_detailed.py b/src/gcore/types/cloud/cost_report_detailed.py index 348099af..abec9401 100644 --- a/src/gcore/types/cloud/cost_report_detailed.py +++ b/src/gcore/types/cloud/cost_report_detailed.py @@ -368,7 +368,7 @@ class ResultResourceEgressTrafficWithCostSerializer(BaseModel): instance_name: Optional[str] = None """Name of the instance""" - instance_type: Literal["baremetal", "vm"] + instance_type: Literal["baremetal", "router", "vm"] """Type of the instance""" last_seen: datetime diff --git a/src/gcore/types/cloud/ddos_profile_field.py b/src/gcore/types/cloud/ddos_profile_field.py index 0bcfbca1..2e325199 100644 --- a/src/gcore/types/cloud/ddos_profile_field.py +++ b/src/gcore/types/cloud/ddos_profile_field.py @@ -11,7 +11,7 @@ class DDOSProfileField(BaseModel): id: int """Unique identifier for the DDoS protection field""" - base_field: Optional[int] = None + base_field: int """ID of DDoS profile field""" default: Optional[str] = None @@ -20,14 +20,11 @@ class DDOSProfileField(BaseModel): description: Optional[str] = None """Detailed description explaining the field's purpose and usage guidelines""" - field_name: Optional[str] = None - """Name of DDoS profile field""" - field_type: Optional[str] = None """Data type classification of the field (e.g., string, integer, array)""" field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" name: str """Human-readable name of the protection field""" @@ -39,6 +36,3 @@ class DDOSProfileField(BaseModel): validation_schema: object """JSON schema defining validation rules and constraints for the field value""" - - value: Optional[str] = None - """Basic type value. Only one of 'value' or 'field_value' must be specified.""" diff --git a/src/gcore/types/cloud/gpu_baremetal/clusters/interface_attach_params.py b/src/gcore/types/cloud/gpu_baremetal/clusters/interface_attach_params.py index f3a3c26e..9dd695aa 100644 --- a/src/gcore/types/cloud/gpu_baremetal/clusters/interface_attach_params.py +++ b/src/gcore/types/cloud/gpu_baremetal/clusters/interface_attach_params.py @@ -51,14 +51,11 @@ class NewInterfaceExternalExtendSchemaWithDDOS(TypedDict, total=False): class NewInterfaceExternalExtendSchemaWithDdosddosProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" @@ -109,14 +106,11 @@ class NewInterfaceSpecificSubnetSchema(TypedDict, total=False): class NewInterfaceSpecificSubnetSchemaDDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" @@ -170,14 +164,11 @@ class NewInterfaceAnySubnetSchema(TypedDict, total=False): class NewInterfaceAnySubnetSchemaDDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" @@ -228,14 +219,11 @@ class NewInterfaceReservedFixedIPSchema(TypedDict, total=False): class NewInterfaceReservedFixedIPSchemaDDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" diff --git a/src/gcore/types/cloud/image.py b/src/gcore/types/cloud/image.py index 901b0436..b7775975 100644 --- a/src/gcore/types/cloud/image.py +++ b/src/gcore/types/cloud/image.py @@ -14,12 +14,44 @@ class Image(BaseModel): id: str """Image ID""" + architecture: Literal["aarch64", "x86_64"] + """An image architecture type: aarch64, `x86_64`.""" + created_at: datetime """Datetime when the image was created""" + creator_task_id: Optional[str] = None + """Task that created this entity""" + + currency_code: Optional[str] = None + """Currency code. Shown if the `include_prices` query parameter if set to true""" + + description: Optional[str] = None + """Image description""" + disk_format: str """Disk format""" + display_order: Optional[int] = None + + gpu_driver: Optional[str] = None + """Name of the GPU driver vendor""" + + gpu_driver_type: Optional[str] = None + """Type of the GPU driver""" + + gpu_driver_version: Optional[str] = None + """Version of the installed GPU driver""" + + hw_firmware_type: Optional[Literal["bios", "uefi"]] = None + """Specifies the type of firmware with which to boot the guest.""" + + hw_machine_type: Optional[Literal["pc", "q35"]] = None + """A virtual chipset type.""" + + is_baremetal: bool + """Set to true if the image will be used by bare metal servers.""" + min_disk: int """Minimal boot volume required""" @@ -38,6 +70,15 @@ class Image(BaseModel): os_version: str """OS version, i.e. 19.04 (for Ubuntu) or 9.4 for Debian""" + price_per_hour: Optional[float] = None + """Price per hour. Shown if the `include_prices` query parameter if set to true""" + + price_per_month: Optional[float] = None + """Price per month. Shown if the `include_prices` query parameter if set to true""" + + price_status: Optional[Literal["error", "hide", "show"]] = None + """Price status for the UI""" + project_id: int """Project ID""" @@ -47,9 +88,12 @@ class Image(BaseModel): region_id: int """Region ID""" - size: int + size: Optional[int] = None """Image size in bytes""" + ssh_key: Optional[Literal["allow", "deny", "required"]] = None + """Whether the image supports SSH key or not""" + status: str """Image status, i.e. active""" @@ -63,47 +107,15 @@ class Image(BaseModel): values. """ - updated_at: datetime - """Datetime when the image was updated""" - - visibility: str - """Image visibility. Globally visible images are public""" - - architecture: Optional[Literal["aarch64", "x86_64"]] = None - """An image architecture type: aarch64, `x86_64`""" - - creator_task_id: Optional[str] = None - """Task that created this entity""" - - description: Optional[str] = None - """Image description""" - - display_order: Optional[int] = None - - gpu_driver: Optional[str] = None - """Name of the GPU driver vendor""" - - gpu_driver_type: Optional[str] = None - """Type of the GPU driver""" - - gpu_driver_version: Optional[str] = None - """Version of the installed GPU driver""" - - hw_firmware_type: Optional[Literal["bios", "uefi"]] = None - """Specifies the type of firmware with which to boot the guest.""" - - hw_machine_type: Optional[Literal["pc", "q35"]] = None - """A virtual chipset type.""" - - is_baremetal: Optional[bool] = None - """Set to true if the image will be used by bare metal servers. Defaults to false.""" - - ssh_key: Optional[Literal["allow", "deny", "required"]] = None - """Whether the image supports SSH key or not""" - task_id: Optional[str] = None """The UUID of the active task that currently holds a lock on the resource. This lock prevents concurrent modifications to ensure consistency. If `null`, the resource is not locked. """ + + updated_at: datetime + """Datetime when the image was updated""" + + visibility: str + """Image visibility. Globally visible images are public""" diff --git a/src/gcore/types/cloud/instances/image_create_from_volume_params.py b/src/gcore/types/cloud/instances/image_create_from_volume_params.py index d98e34ae..beb3f64f 100644 --- a/src/gcore/types/cloud/instances/image_create_from_volume_params.py +++ b/src/gcore/types/cloud/instances/image_create_from_volume_params.py @@ -10,8 +10,10 @@ class ImageCreateFromVolumeParams(TypedDict, total=False): project_id: int + """Project ID""" region_id: int + """Region ID""" name: Required[str] """Image name""" diff --git a/src/gcore/types/cloud/instances/image_list_params.py b/src/gcore/types/cloud/instances/image_list_params.py index 1e1d2903..db3e8854 100644 --- a/src/gcore/types/cloud/instances/image_list_params.py +++ b/src/gcore/types/cloud/instances/image_list_params.py @@ -11,20 +11,22 @@ class ImageListParams(TypedDict, total=False): project_id: int + """Project ID""" region_id: int + """Region ID""" include_prices: bool - """Show price""" + """Show price.""" private: str """Any value to show private images""" tag_key: SequenceNotStr[str] - """Filter by tag keys.""" + """Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2""" tag_key_value: str - """Filter by tag key-value pairs. Must be a valid JSON string.""" + """Optional. Filter by tag key-value pairs.""" visibility: Literal["private", "public", "shared"] """Image visibility. Globally visible images are public""" diff --git a/src/gcore/types/cloud/instances/interface_attach_params.py b/src/gcore/types/cloud/instances/interface_attach_params.py index f3a3c26e..9dd695aa 100644 --- a/src/gcore/types/cloud/instances/interface_attach_params.py +++ b/src/gcore/types/cloud/instances/interface_attach_params.py @@ -51,14 +51,11 @@ class NewInterfaceExternalExtendSchemaWithDDOS(TypedDict, total=False): class NewInterfaceExternalExtendSchemaWithDdosddosProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" @@ -109,14 +106,11 @@ class NewInterfaceSpecificSubnetSchema(TypedDict, total=False): class NewInterfaceSpecificSubnetSchemaDDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" @@ -170,14 +164,11 @@ class NewInterfaceAnySubnetSchema(TypedDict, total=False): class NewInterfaceAnySubnetSchemaDDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" @@ -228,14 +219,11 @@ class NewInterfaceReservedFixedIPSchema(TypedDict, total=False): class NewInterfaceReservedFixedIPSchemaDDOSProfileField(TypedDict, total=False): - base_field: Optional[int] + base_field: Required[int] """ID of DDoS profile field""" - field_name: Optional[str] - """Name of DDoS profile field""" - field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified.""" + """Complex value for the DDoS profile field""" value: Optional[str] """Basic type value. Only one of 'value' or 'field_value' must be specified.""" diff --git a/src/gcore/types/cloud/k8s/cluster_create_params.py b/src/gcore/types/cloud/k8s/cluster_create_params.py index 6888e441..380c73fc 100644 --- a/src/gcore/types/cloud/k8s/cluster_create_params.py +++ b/src/gcore/types/cloud/k8s/cluster_create_params.py @@ -327,10 +327,7 @@ class DDOSProfileField(TypedDict, total=False): base_field: Required[int] field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified""" - - value: Optional[str] - """Basic value. Only one of 'value' or 'field_value' must be specified""" + """Complex value for the DDoS profile field""" class DDOSProfile(TypedDict, total=False): diff --git a/src/gcore/types/cloud/k8s/cluster_update_params.py b/src/gcore/types/cloud/k8s/cluster_update_params.py index 911a71e8..52aaea13 100644 --- a/src/gcore/types/cloud/k8s/cluster_update_params.py +++ b/src/gcore/types/cloud/k8s/cluster_update_params.py @@ -237,10 +237,7 @@ class DDOSProfileField(TypedDict, total=False): base_field: Required[int] field_value: object - """Complex value. Only one of 'value' or 'field_value' must be specified""" - - value: Optional[str] - """Basic value. Only one of 'value' or 'field_value' must be specified""" + """Complex value for the DDoS profile field""" class DDOSProfile(TypedDict, total=False): diff --git a/src/gcore/types/cloud/k8s/k8s_cluster.py b/src/gcore/types/cloud/k8s/k8s_cluster.py index 283626ba..3780a73f 100644 --- a/src/gcore/types/cloud/k8s/k8s_cluster.py +++ b/src/gcore/types/cloud/k8s/k8s_cluster.py @@ -162,10 +162,7 @@ class DDOSProfileField(BaseModel): base_field: int field_value: Optional[object] = None - """Complex value. Only one of 'value' or 'field_value' must be specified""" - - value: Optional[str] = None - """Basic value. Only one of 'value' or 'field_value' must be specified""" + """Complex value for the DDoS profile field""" class DDOSProfile(BaseModel): diff --git a/src/gcore/types/cloud/region.py b/src/gcore/types/cloud/region.py index b7b24fbe..5b95eb78 100644 --- a/src/gcore/types/cloud/region.py +++ b/src/gcore/types/cloud/region.py @@ -87,6 +87,9 @@ class Region(BaseModel): metrics_database_id: Optional[int] = None """Foreign key to Metrics database entity""" + slug: str + """Short, human-readable region identifier (e.g. luxembourg-2, santa-clara-1).""" + state: Literal["ACTIVE", "DELETED", "DELETING", "DELETION_FAILED", "INACTIVE", "MAINTENANCE", "NEW"] """Region state""" diff --git a/src/gcore/types/cloud/usage_report.py b/src/gcore/types/cloud/usage_report.py index 659a5421..a99546dd 100644 --- a/src/gcore/types/cloud/usage_report.py +++ b/src/gcore/types/cloud/usage_report.py @@ -346,7 +346,7 @@ class ResourceResourceEgressTrafficSerializer(BaseModel): instance_name: Optional[str] = None """Name of the instance""" - instance_type: Literal["baremetal", "vm"] + instance_type: Literal["baremetal", "router", "vm"] """Type of the instance""" last_seen: datetime @@ -1290,7 +1290,7 @@ class TotalTotalEgressTrafficReportItemSerializer(BaseModel): billing_value_unit: Literal["bytes"] """Unit of billing value""" - instance_type: Literal["baremetal", "vm"] + instance_type: Literal["baremetal", "router", "vm"] """Type of the instance""" region: int diff --git a/tests/api_resources/cloud/baremetal/test_images.py b/tests/api_resources/cloud/baremetal/test_images.py index 235f970b..8e7a6db8 100644 --- a/tests/api_resources/cloud/baremetal/test_images.py +++ b/tests/api_resources/cloud/baremetal/test_images.py @@ -9,7 +9,7 @@ from gcore import Gcore, AsyncGcore from tests.utils import assert_matches_type -from gcore.types.cloud import ImageList +from gcore.types.cloud.baremetal import ImageListResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -20,47 +20,47 @@ class TestImages: @parametrize def test_method_list(self, client: Gcore) -> None: image = client.cloud.baremetal.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: image = client.cloud.baremetal.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, include_prices=True, private="private", - tag_key=["string"], + tag_key=["key1", "key2"], tag_key_value="tag_key_value", visibility="private", ) - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) @parametrize def test_raw_response_list(self, client: Gcore) -> None: response = client.cloud.baremetal.images.with_raw_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) @parametrize def test_streaming_response_list(self, client: Gcore) -> None: with client.cloud.baremetal.images.with_streaming_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) assert cast(Any, response.is_closed) is True @@ -73,46 +73,46 @@ class TestAsyncImages: @parametrize async def test_method_list(self, async_client: AsyncGcore) -> None: image = await async_client.cloud.baremetal.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: image = await async_client.cloud.baremetal.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, include_prices=True, private="private", - tag_key=["string"], + tag_key=["key1", "key2"], tag_key_value="tag_key_value", visibility="private", ) - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.baremetal.images.with_raw_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = await response.parse() - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: async with async_client.cloud.baremetal.images.with_streaming_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = await response.parse() - assert_matches_type(ImageList, image, path=["response"]) + assert_matches_type(ImageListResponse, image, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/cloud/baremetal/test_servers.py b/tests/api_resources/cloud/baremetal/test_servers.py index a165f795..4897a5a6 100644 --- a/tests/api_resources/cloud/baremetal/test_servers.py +++ b/tests/api_resources/cloud/baremetal/test_servers.py @@ -54,7 +54,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: { "base_field": 10, "field_value": [45046, 45047], - "value": None, } ], }, @@ -418,7 +417,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> { "base_field": 10, "field_value": [45046, 45047], - "value": None, } ], }, diff --git a/tests/api_resources/cloud/gpu_baremetal/clusters/test_interfaces.py b/tests/api_resources/cloud/gpu_baremetal/clusters/test_interfaces.py index de330f4d..650f2175 100644 --- a/tests/api_resources/cloud/gpu_baremetal/clusters/test_interfaces.py +++ b/tests/api_resources/cloud/gpu_baremetal/clusters/test_interfaces.py @@ -83,9 +83,8 @@ def test_method_attach_with_all_params_overload_1(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -160,9 +159,8 @@ def test_method_attach_with_all_params_overload_2(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -239,9 +237,8 @@ def test_method_attach_with_all_params_overload_3(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -319,9 +316,8 @@ def test_method_attach_with_all_params_overload_4(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -502,9 +498,8 @@ async def test_method_attach_with_all_params_overload_1(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -579,9 +574,8 @@ async def test_method_attach_with_all_params_overload_2(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -658,9 +652,8 @@ async def test_method_attach_with_all_params_overload_3(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -738,9 +731,8 @@ async def test_method_attach_with_all_params_overload_4(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", diff --git a/tests/api_resources/cloud/instances/test_images.py b/tests/api_resources/cloud/instances/test_images.py index 45679a8f..1bbc238c 100644 --- a/tests/api_resources/cloud/instances/test_images.py +++ b/tests/api_resources/cloud/instances/test_images.py @@ -82,19 +82,19 @@ def test_path_params_update(self, client: Gcore) -> None: @parametrize def test_method_list(self, client: Gcore) -> None: image = client.cloud.instances.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) assert_matches_type(ImageList, image, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: image = client.cloud.instances.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, include_prices=True, private="private", - tag_key=["string"], + tag_key=["key1", "key2"], tag_key_value="tag_key_value", visibility="private", ) @@ -103,8 +103,8 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: @parametrize def test_raw_response_list(self, client: Gcore) -> None: response = client.cloud.instances.images.with_raw_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) assert response.is_closed is True @@ -115,8 +115,8 @@ def test_raw_response_list(self, client: Gcore) -> None: @parametrize def test_streaming_response_list(self, client: Gcore) -> None: with client.cloud.instances.images.with_streaming_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -175,8 +175,8 @@ def test_path_params_delete(self, client: Gcore) -> None: @parametrize def test_method_create_from_volume(self, client: Gcore) -> None: image = client.cloud.instances.images.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", ) @@ -185,8 +185,8 @@ def test_method_create_from_volume(self, client: Gcore) -> None: @parametrize def test_method_create_from_volume_with_all_params(self, client: Gcore) -> None: image = client.cloud.instances.images.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", architecture="x86_64", @@ -203,8 +203,8 @@ def test_method_create_from_volume_with_all_params(self, client: Gcore) -> None: @parametrize def test_raw_response_create_from_volume(self, client: Gcore) -> None: response = client.cloud.instances.images.with_raw_response.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", ) @@ -217,8 +217,8 @@ def test_raw_response_create_from_volume(self, client: Gcore) -> None: @parametrize def test_streaming_response_create_from_volume(self, client: Gcore) -> None: with client.cloud.instances.images.with_streaming_response.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", ) as response: @@ -417,19 +417,19 @@ async def test_path_params_update(self, async_client: AsyncGcore) -> None: @parametrize async def test_method_list(self, async_client: AsyncGcore) -> None: image = await async_client.cloud.instances.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) assert_matches_type(ImageList, image, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: image = await async_client.cloud.instances.images.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, include_prices=True, private="private", - tag_key=["string"], + tag_key=["key1", "key2"], tag_key_value="tag_key_value", visibility="private", ) @@ -438,8 +438,8 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.instances.images.with_raw_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) assert response.is_closed is True @@ -450,8 +450,8 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: async with async_client.cloud.instances.images.with_streaming_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=7, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -510,8 +510,8 @@ async def test_path_params_delete(self, async_client: AsyncGcore) -> None: @parametrize async def test_method_create_from_volume(self, async_client: AsyncGcore) -> None: image = await async_client.cloud.instances.images.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", ) @@ -520,8 +520,8 @@ async def test_method_create_from_volume(self, async_client: AsyncGcore) -> None @parametrize async def test_method_create_from_volume_with_all_params(self, async_client: AsyncGcore) -> None: image = await async_client.cloud.instances.images.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", architecture="x86_64", @@ -538,8 +538,8 @@ async def test_method_create_from_volume_with_all_params(self, async_client: Asy @parametrize async def test_raw_response_create_from_volume(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.instances.images.with_raw_response.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", ) @@ -552,8 +552,8 @@ async def test_raw_response_create_from_volume(self, async_client: AsyncGcore) - @parametrize async def test_streaming_response_create_from_volume(self, async_client: AsyncGcore) -> None: async with async_client.cloud.instances.images.with_streaming_response.create_from_volume( - project_id=0, - region_id=0, + project_id=1, + region_id=7, name="my-image", volume_id="d478ae29-dedc-4869-82f0-96104425f565", ) as response: diff --git a/tests/api_resources/cloud/instances/test_interfaces.py b/tests/api_resources/cloud/instances/test_interfaces.py index 348e946d..6d2c8252 100644 --- a/tests/api_resources/cloud/instances/test_interfaces.py +++ b/tests/api_resources/cloud/instances/test_interfaces.py @@ -83,9 +83,8 @@ def test_method_attach_with_all_params_overload_1(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -160,9 +159,8 @@ def test_method_attach_with_all_params_overload_2(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -239,9 +237,8 @@ def test_method_attach_with_all_params_overload_3(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -319,9 +316,8 @@ def test_method_attach_with_all_params_overload_4(self, client: Gcore) -> None: "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -502,9 +498,8 @@ async def test_method_attach_with_all_params_overload_1(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -579,9 +574,8 @@ async def test_method_attach_with_all_params_overload_2(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -658,9 +652,8 @@ async def test_method_attach_with_all_params_overload_3(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", @@ -738,9 +731,8 @@ async def test_method_attach_with_all_params_overload_4(self, async_client: Asyn "fields": [ { "base_field": 10, - "field_name": "field_name", "field_value": [45046, 45047], - "value": None, + "value": "value", } ], "profile_template_name": "profile_template_name", diff --git a/tests/api_resources/cloud/k8s/test_clusters.py b/tests/api_resources/cloud/k8s/test_clusters.py index eb78a0b2..353b26ab 100644 --- a/tests/api_resources/cloud/k8s/test_clusters.py +++ b/tests/api_resources/cloud/k8s/test_clusters.py @@ -107,7 +107,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: { "base_field": 10, "field_value": [45046, 45047], - "value": None, } ], "profile_template": 29, @@ -231,7 +230,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: { "base_field": 10, "field_value": [45046, 45047], - "value": None, } ], "profile_template": 29, @@ -652,7 +650,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> { "base_field": 10, "field_value": [45046, 45047], - "value": None, } ], "profile_template": 29, @@ -776,7 +773,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> { "base_field": 10, "field_value": [45046, 45047], - "value": None, } ], "profile_template": 29, From fce124a98b3eb6cec5bbcb46e60e424b4c6da298 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 13:40:53 +0000 Subject: [PATCH 03/19] refactor(cloud): split instance and baremetal image models --- .stats.yml | 6 +++--- src/gcore/resources/cloud/api.md | 18 +++++++++++------- src/gcore/resources/cloud/baremetal/images.py | 10 +++++----- src/gcore/resources/cloud/instances/images.py | 4 ++-- src/gcore/types/cloud/__init__.py | 2 -- src/gcore/types/cloud/baremetal/__init__.py | 3 ++- ...age_list_response.py => baremetal_image.py} | 12 ++---------- .../cloud/baremetal/baremetal_image_list.py | 16 ++++++++++++++++ src/gcore/types/cloud/instances/__init__.py | 2 ++ src/gcore/types/cloud/{ => instances}/image.py | 4 ++-- .../types/cloud/{ => instances}/image_list.py | 2 +- .../cloud/baremetal/test_images.py | 18 +++++++++--------- .../cloud/instances/test_images.py | 6 +++++- 13 files changed, 60 insertions(+), 43 deletions(-) rename src/gcore/types/cloud/baremetal/{image_list_response.py => baremetal_image.py} (94%) create mode 100644 src/gcore/types/cloud/baremetal/baremetal_image_list.py rename src/gcore/types/cloud/{ => instances}/image.py (98%) rename src/gcore/types/cloud/{ => instances}/image_list.py (89%) diff --git a/.stats.yml b/.stats.yml index 4404c997..7abdd5c7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 657 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-c30b070f2f0ce73f80dfb6c950d3e46672acaedef0a7f011e75065477779bdf6.yml -openapi_spec_hash: 56a6b5ca8f97983419b03f5e33b36594 -config_hash: ab6b5443d52ca04e4e0e12def131f8e6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-2c51dec6cb0178c3e94e32aaf2ccb1627fdc815fa181a2c053861e1ccf1392bb.yml +openapi_spec_hash: a763e3bba4d1827b1ebf28b59ceebfe4 +config_hash: a9b5cf71cf792435a76f72d3856c8ee0 diff --git a/src/gcore/resources/cloud/api.md b/src/gcore/resources/cloud/api.md index 40c43971..6f43d248 100644 --- a/src/gcore/resources/cloud/api.md +++ b/src/gcore/resources/cloud/api.md @@ -24,8 +24,6 @@ from gcore.types.cloud import ( GPUImage, GPUImageList, HTTPMethod, - Image, - ImageList, Instance, InstanceIsolation, InstanceList, @@ -649,12 +647,12 @@ Methods: Types: ```python -from gcore.types.cloud.baremetal import ImageListResponse +from gcore.types.cloud.baremetal import BaremetalImage, BaremetalImageList ``` Methods: -- client.cloud.baremetal.images.list(\*, project_id, region_id, \*\*params) -> ImageListResponse +- client.cloud.baremetal.images.list(\*, project_id, region_id, \*\*params) -> BaremetalImageList ### Flavors @@ -999,13 +997,19 @@ Methods: ### Images +Types: + +```python +from gcore.types.cloud.instances import Image, ImageList +``` + Methods: -- client.cloud.instances.images.update(image_id, \*, project_id, region_id, \*\*params) -> Image -- client.cloud.instances.images.list(\*, project_id, region_id, \*\*params) -> ImageList +- client.cloud.instances.images.update(image_id, \*, project_id, region_id, \*\*params) -> Image +- client.cloud.instances.images.list(\*, project_id, region_id, \*\*params) -> ImageList - client.cloud.instances.images.delete(image_id, \*, project_id, region_id) -> TaskIDList - client.cloud.instances.images.create_from_volume(\*, project_id, region_id, \*\*params) -> TaskIDList -- client.cloud.instances.images.get(image_id, \*, project_id, region_id, \*\*params) -> Image +- client.cloud.instances.images.get(image_id, \*, project_id, region_id, \*\*params) -> Image - client.cloud.instances.images.upload(\*, project_id, region_id, \*\*params) -> TaskIDList ### Metrics diff --git a/src/gcore/resources/cloud/baremetal/images.py b/src/gcore/resources/cloud/baremetal/images.py index f0bac9bc..6cfd7302 100644 --- a/src/gcore/resources/cloud/baremetal/images.py +++ b/src/gcore/resources/cloud/baremetal/images.py @@ -18,7 +18,7 @@ ) from ...._base_client import make_request_options from ....types.cloud.baremetal import image_list_params -from ....types.cloud.baremetal.image_list_response import ImageListResponse +from ....types.cloud.baremetal.baremetal_image_list import BaremetalImageList __all__ = ["ImagesResource", "AsyncImagesResource"] @@ -59,7 +59,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ImageListResponse: + ) -> BaremetalImageList: """Retrieve a list of available images for bare metal servers. The list can be @@ -111,7 +111,7 @@ def list( image_list_params.ImageListParams, ), ), - cast_to=ImageListResponse, + cast_to=BaremetalImageList, ) @@ -151,7 +151,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ImageListResponse: + ) -> BaremetalImageList: """Retrieve a list of available images for bare metal servers. The list can be @@ -203,7 +203,7 @@ async def list( image_list_params.ImageListParams, ), ), - cast_to=ImageListResponse, + cast_to=BaremetalImageList, ) diff --git a/src/gcore/resources/cloud/instances/images.py b/src/gcore/resources/cloud/instances/images.py index 8fe2f588..97d689cf 100644 --- a/src/gcore/resources/cloud/instances/images.py +++ b/src/gcore/resources/cloud/instances/images.py @@ -18,7 +18,6 @@ async_to_streamed_response_wrapper, ) from ...._base_client import make_request_options -from ....types.cloud.image import Image from ....types.cloud.instances import ( image_get_params, image_list_params, @@ -26,8 +25,9 @@ image_upload_params, image_create_from_volume_params, ) -from ....types.cloud.image_list import ImageList from ....types.cloud.task_id_list import TaskIDList +from ....types.cloud.instances.image import Image +from ....types.cloud.instances.image_list import ImageList from ....types.cloud.tag_update_map_param import TagUpdateMapParam __all__ = ["ImagesResource", "AsyncImagesResource"] diff --git a/src/gcore/types/cloud/__init__.py b/src/gcore/types/cloud/__init__.py index 450b32e1..bf9dea98 100644 --- a/src/gcore/types/cloud/__init__.py +++ b/src/gcore/types/cloud/__init__.py @@ -4,7 +4,6 @@ from .tag import Tag as Tag from .task import Task as Task -from .image import Image as Image from .route import Route as Route from .member import Member as Member from .region import Region as Region @@ -22,7 +21,6 @@ from .gpu_image import GPUImage as GPUImage from .ip_ranges import IPRanges as IPRanges from .file_share import FileShare as FileShare -from .image_list import ImageList as ImageList from .ip_version import IPVersion as IPVersion from .floating_ip import FloatingIP as FloatingIP from .http_method import HTTPMethod as HTTPMethod diff --git a/src/gcore/types/cloud/baremetal/__init__.py b/src/gcore/types/cloud/baremetal/__init__.py index 7bef20d4..845e2578 100644 --- a/src/gcore/types/cloud/baremetal/__init__.py +++ b/src/gcore/types/cloud/baremetal/__init__.py @@ -2,11 +2,12 @@ from __future__ import annotations +from .baremetal_image import BaremetalImage as BaremetalImage from .baremetal_server import BaremetalServer as BaremetalServer from .image_list_params import ImageListParams as ImageListParams from .flavor_list_params import FlavorListParams as FlavorListParams from .server_list_params import ServerListParams as ServerListParams -from .image_list_response import ImageListResponse as ImageListResponse +from .baremetal_image_list import BaremetalImageList as BaremetalImageList from .server_create_params import ServerCreateParams as ServerCreateParams from .server_delete_params import ServerDeleteParams as ServerDeleteParams from .server_update_params import ServerUpdateParams as ServerUpdateParams diff --git a/src/gcore/types/cloud/baremetal/image_list_response.py b/src/gcore/types/cloud/baremetal/baremetal_image.py similarity index 94% rename from src/gcore/types/cloud/baremetal/image_list_response.py rename to src/gcore/types/cloud/baremetal/baremetal_image.py index 81c15f77..91a534d1 100644 --- a/src/gcore/types/cloud/baremetal/image_list_response.py +++ b/src/gcore/types/cloud/baremetal/baremetal_image.py @@ -7,10 +7,10 @@ from ..tag import Tag from ...._models import BaseModel -__all__ = ["ImageListResponse", "Result"] +__all__ = ["BaremetalImage"] -class Result(BaseModel): +class BaremetalImage(BaseModel): id: str """Image ID""" @@ -119,11 +119,3 @@ class Result(BaseModel): visibility: str """Image visibility. Globally visible images are public""" - - -class ImageListResponse(BaseModel): - count: int - """Number of objects""" - - results: List[Result] - """Objects""" diff --git a/src/gcore/types/cloud/baremetal/baremetal_image_list.py b/src/gcore/types/cloud/baremetal/baremetal_image_list.py new file mode 100644 index 00000000..643c1941 --- /dev/null +++ b/src/gcore/types/cloud/baremetal/baremetal_image_list.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List + +from ...._models import BaseModel +from .baremetal_image import BaremetalImage + +__all__ = ["BaremetalImageList"] + + +class BaremetalImageList(BaseModel): + count: int + """Number of objects""" + + results: List[BaremetalImage] + """Objects""" diff --git a/src/gcore/types/cloud/instances/__init__.py b/src/gcore/types/cloud/instances/__init__.py index fb06e51e..85c26ce7 100644 --- a/src/gcore/types/cloud/instances/__init__.py +++ b/src/gcore/types/cloud/instances/__init__.py @@ -2,7 +2,9 @@ from __future__ import annotations +from .image import Image as Image from .metrics import Metrics as Metrics +from .image_list import ImageList as ImageList from .metrics_list import MetricsList as MetricsList from .image_get_params import ImageGetParams as ImageGetParams from .image_list_params import ImageListParams as ImageListParams diff --git a/src/gcore/types/cloud/image.py b/src/gcore/types/cloud/instances/image.py similarity index 98% rename from src/gcore/types/cloud/image.py rename to src/gcore/types/cloud/instances/image.py index b7775975..823b6ce5 100644 --- a/src/gcore/types/cloud/image.py +++ b/src/gcore/types/cloud/instances/image.py @@ -4,8 +4,8 @@ from datetime import datetime from typing_extensions import Literal -from .tag import Tag -from ..._models import BaseModel +from ..tag import Tag +from ...._models import BaseModel __all__ = ["Image"] diff --git a/src/gcore/types/cloud/image_list.py b/src/gcore/types/cloud/instances/image_list.py similarity index 89% rename from src/gcore/types/cloud/image_list.py rename to src/gcore/types/cloud/instances/image_list.py index 4a4981fc..07ea5687 100644 --- a/src/gcore/types/cloud/image_list.py +++ b/src/gcore/types/cloud/instances/image_list.py @@ -3,7 +3,7 @@ from typing import List from .image import Image -from ..._models import BaseModel +from ...._models import BaseModel __all__ = ["ImageList"] diff --git a/tests/api_resources/cloud/baremetal/test_images.py b/tests/api_resources/cloud/baremetal/test_images.py index 8e7a6db8..0d047bbd 100644 --- a/tests/api_resources/cloud/baremetal/test_images.py +++ b/tests/api_resources/cloud/baremetal/test_images.py @@ -9,7 +9,7 @@ from gcore import Gcore, AsyncGcore from tests.utils import assert_matches_type -from gcore.types.cloud.baremetal import ImageListResponse +from gcore.types.cloud.baremetal import BaremetalImageList base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,7 +23,7 @@ def test_method_list(self, client: Gcore) -> None: project_id=1, region_id=7, ) - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: @@ -36,7 +36,7 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: tag_key_value="tag_key_value", visibility="private", ) - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) @parametrize def test_raw_response_list(self, client: Gcore) -> None: @@ -48,7 +48,7 @@ def test_raw_response_list(self, client: Gcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) @parametrize def test_streaming_response_list(self, client: Gcore) -> None: @@ -60,7 +60,7 @@ def test_streaming_response_list(self, client: Gcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = response.parse() - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) assert cast(Any, response.is_closed) is True @@ -76,7 +76,7 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: project_id=1, region_id=7, ) - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: @@ -89,7 +89,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No tag_key_value="tag_key_value", visibility="private", ) - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @@ -101,7 +101,7 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = await response.parse() - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: @@ -113,6 +113,6 @@ async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" image = await response.parse() - assert_matches_type(ImageListResponse, image, path=["response"]) + assert_matches_type(BaremetalImageList, image, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/cloud/instances/test_images.py b/tests/api_resources/cloud/instances/test_images.py index 1bbc238c..036159e2 100644 --- a/tests/api_resources/cloud/instances/test_images.py +++ b/tests/api_resources/cloud/instances/test_images.py @@ -9,7 +9,11 @@ from gcore import Gcore, AsyncGcore from tests.utils import assert_matches_type -from gcore.types.cloud import Image, ImageList, TaskIDList +from gcore.types.cloud import TaskIDList +from gcore.types.cloud.instances import ( + Image, + ImageList, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") From 7e924f830c234be00e8c6cb940fe59ff4629a626 Mon Sep 17 00:00:00 2001 From: Pedro Oliveira <8281907+pedrodeoliveira@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:57:38 +0100 Subject: [PATCH 04/19] fix(examples): update baremetal image examples to use BaremetalImage type The image models were moved from the shared cloud scope to specific subresources, but the baremetal examples were still referencing cloud.Image instead of cloud.baremetal.BaremetalImage. --- examples/cloud/baremetal.py | 12 ++++++------ examples/cloud/baremetal_async.py | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/cloud/baremetal.py b/examples/cloud/baremetal.py index 07cc9966..d9f1a51d 100644 --- a/examples/cloud/baremetal.py +++ b/examples/cloud/baremetal.py @@ -1,8 +1,8 @@ from typing import List from gcore import Gcore -from gcore.types.cloud.image import Image from gcore.types.cloud.baremetal_flavor import BaremetalFlavor +from gcore.types.cloud.baremetal.baremetal_image import BaremetalImage from gcore.types.cloud.baremetal.server_create_params import ( InterfaceCreateBareMetalExternalInterfaceSerializer, ) @@ -80,7 +80,7 @@ def list_flavors(*, client: Gcore) -> List[BaremetalFlavor]: return flavors.results -def list_images(*, client: Gcore) -> List[Image]: +def list_images(*, client: Gcore) -> List[BaremetalImage]: print("\n=== LIST BAREMETAL IMAGES ===") images = client.cloud.baremetal.images.list() _print_image_details(images.results) @@ -108,7 +108,7 @@ def _print_flavor_details(flavors: List[BaremetalFlavor]) -> None: print(f" ... and {len(flavors) - display_count} more flavors") -def _print_image_details(images: List[Image]) -> None: +def _print_image_details(images: List[BaremetalImage]) -> None: display_count = 3 if len(images) < display_count: display_count = len(images) @@ -133,15 +133,15 @@ def _get_smallest_flavor(flavors: List[BaremetalFlavor]) -> str: return smallest_flavor.flavor_id -def _get_ubuntu_image(images: List[Image]) -> str: +def _get_ubuntu_image(images: List[BaremetalImage]) -> str: return _get_os_image(images, "ubuntu") -def _get_debian_image(images: List[Image]) -> str: +def _get_debian_image(images: List[BaremetalImage]) -> str: return _get_os_image(images, "debian") -def _get_os_image(images: List[Image], os_name: str) -> str: +def _get_os_image(images: List[BaremetalImage], os_name: str) -> str: os_images = [img for img in images if os_name.lower() in img.name.lower()] if not os_images: linux_images = [img for img in images if img.os_type.lower() == "linux"] diff --git a/examples/cloud/baremetal_async.py b/examples/cloud/baremetal_async.py index 2e1ec482..bea2ec6d 100644 --- a/examples/cloud/baremetal_async.py +++ b/examples/cloud/baremetal_async.py @@ -2,8 +2,8 @@ from typing import List from gcore import AsyncGcore -from gcore.types.cloud.image import Image from gcore.types.cloud.baremetal_flavor import BaremetalFlavor +from gcore.types.cloud.baremetal.baremetal_image import BaremetalImage from gcore.types.cloud.baremetal.server_create_params import ( InterfaceCreateBareMetalExternalInterfaceSerializer, ) @@ -83,7 +83,7 @@ async def list_flavors(*, client: AsyncGcore) -> List[BaremetalFlavor]: return flavors.results -async def list_images(*, client: AsyncGcore) -> List[Image]: +async def list_images(*, client: AsyncGcore) -> List[BaremetalImage]: print("\n=== LIST BAREMETAL IMAGES ===") images = await client.cloud.baremetal.images.list() _print_image_details(images.results) @@ -111,7 +111,7 @@ def _print_flavor_details(flavors: List[BaremetalFlavor]) -> None: print(f" ... and {len(flavors) - display_count} more flavors") -def _print_image_details(images: List[Image]) -> None: +def _print_image_details(images: List[BaremetalImage]) -> None: display_count = 3 if len(images) < display_count: display_count = len(images) @@ -136,15 +136,15 @@ def _get_smallest_flavor(flavors: List[BaremetalFlavor]) -> str: return smallest_flavor.flavor_id -def _get_ubuntu_image(images: List[Image]) -> str: +def _get_ubuntu_image(images: List[BaremetalImage]) -> str: return _get_os_image(images, "ubuntu") -def _get_debian_image(images: List[Image]) -> str: +def _get_debian_image(images: List[BaremetalImage]) -> str: return _get_os_image(images, "debian") -def _get_os_image(images: List[Image], os_name: str) -> str: +def _get_os_image(images: List[BaremetalImage], os_name: str) -> str: os_images = [img for img in images if os_name.lower() in img.name.lower()] if not os_images: linux_images = [img for img in images if img.os_type.lower() == "linux"] From de110478944f7c9dc61ac3481dba876912e05ff6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:04:48 +0000 Subject: [PATCH 05/19] fix(dns): update network-mappings get_by_name to new endpoint path --- .stats.yml | 4 +- src/gcore/resources/dns/api.md | 1 + src/gcore/resources/dns/network_mappings.py | 96 +++++++++++++++++++ .../dns/test_network_mappings.py | 76 +++++++++++++++ 4 files changed, 175 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7abdd5c7..c73293bb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 657 +configured_endpoints: 658 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-2c51dec6cb0178c3e94e32aaf2ccb1627fdc815fa181a2c053861e1ccf1392bb.yml openapi_spec_hash: a763e3bba4d1827b1ebf28b59ceebfe4 -config_hash: a9b5cf71cf792435a76f72d3856c8ee0 +config_hash: 783d3945b23f1d5cf64f8fba8e44c9d7 diff --git a/src/gcore/resources/dns/api.md b/src/gcore/resources/dns/api.md index 63494906..86f3648e 100644 --- a/src/gcore/resources/dns/api.md +++ b/src/gcore/resources/dns/api.md @@ -154,5 +154,6 @@ Methods: - client.dns.network_mappings.list(\*\*params) -> NetworkMappingListResponse - client.dns.network_mappings.delete(id) -> object - client.dns.network_mappings.get(id) -> DNSNetworkMapping +- client.dns.network_mappings.get_by_name(name) -> DNSNetworkMapping - client.dns.network*mappings.import*() -> NetworkMappingImportResponse - client.dns.network_mappings.replace(id, \*\*params) -> object diff --git a/src/gcore/resources/dns/network_mappings.py b/src/gcore/resources/dns/network_mappings.py index ffd44c7a..f7a0bd11 100644 --- a/src/gcore/resources/dns/network_mappings.py +++ b/src/gcore/resources/dns/network_mappings.py @@ -263,6 +263,48 @@ def get( cast_to=DNSNetworkMapping, ) + def get_by_name( + self, + name: 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, + ) -> DNSNetworkMapping: + """ + Get network mapping by name. + + Particular network mapping item info + + Example of request: + + ``` + curl --location --request GET 'https://api.gcore.com/dns/v2/network-mappings/by-name/test-mapping' \\ + --header 'Authorization: Bearer ...' + ``` + + Args: + 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 not name: + raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") + return self._get( + path_template("/dns/v2/network-mappings/by-name/{name}", name=name), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DNSNetworkMapping, + ) + def import_( self, *, @@ -655,6 +697,48 @@ async def get( cast_to=DNSNetworkMapping, ) + async def get_by_name( + self, + name: 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, + ) -> DNSNetworkMapping: + """ + Get network mapping by name. + + Particular network mapping item info + + Example of request: + + ``` + curl --location --request GET 'https://api.gcore.com/dns/v2/network-mappings/by-name/test-mapping' \\ + --header 'Authorization: Bearer ...' + ``` + + Args: + 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 not name: + raise ValueError(f"Expected a non-empty value for `name` but received {name!r}") + return await self._get( + path_template("/dns/v2/network-mappings/by-name/{name}", name=name), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DNSNetworkMapping, + ) + async def import_( self, *, @@ -828,6 +912,9 @@ def __init__(self, network_mappings: NetworkMappingsResource) -> None: self.get = to_raw_response_wrapper( network_mappings.get, ) + self.get_by_name = to_raw_response_wrapper( + network_mappings.get_by_name, + ) self.import_ = to_raw_response_wrapper( network_mappings.import_, ) @@ -852,6 +939,9 @@ def __init__(self, network_mappings: AsyncNetworkMappingsResource) -> None: self.get = async_to_raw_response_wrapper( network_mappings.get, ) + self.get_by_name = async_to_raw_response_wrapper( + network_mappings.get_by_name, + ) self.import_ = async_to_raw_response_wrapper( network_mappings.import_, ) @@ -876,6 +966,9 @@ def __init__(self, network_mappings: NetworkMappingsResource) -> None: self.get = to_streamed_response_wrapper( network_mappings.get, ) + self.get_by_name = to_streamed_response_wrapper( + network_mappings.get_by_name, + ) self.import_ = to_streamed_response_wrapper( network_mappings.import_, ) @@ -900,6 +993,9 @@ def __init__(self, network_mappings: AsyncNetworkMappingsResource) -> None: self.get = async_to_streamed_response_wrapper( network_mappings.get, ) + self.get_by_name = async_to_streamed_response_wrapper( + network_mappings.get_by_name, + ) self.import_ = async_to_streamed_response_wrapper( network_mappings.import_, ) diff --git a/tests/api_resources/dns/test_network_mappings.py b/tests/api_resources/dns/test_network_mappings.py index 90c3706f..158a3f7b 100644 --- a/tests/api_resources/dns/test_network_mappings.py +++ b/tests/api_resources/dns/test_network_mappings.py @@ -158,6 +158,44 @@ def test_streaming_response_get(self, client: Gcore) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_get_by_name(self, client: Gcore) -> None: + network_mapping = client.dns.network_mappings.get_by_name( + "name", + ) + assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) + + @parametrize + def test_raw_response_get_by_name(self, client: Gcore) -> None: + response = client.dns.network_mappings.with_raw_response.get_by_name( + "name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_mapping = response.parse() + assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) + + @parametrize + def test_streaming_response_get_by_name(self, client: Gcore) -> None: + with client.dns.network_mappings.with_streaming_response.get_by_name( + "name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_mapping = response.parse() + assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_get_by_name(self, client: Gcore) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"): + client.dns.network_mappings.with_raw_response.get_by_name( + "", + ) + @parametrize def test_method_import(self, client: Gcore) -> None: network_mapping = client.dns.network_mappings.import_() @@ -371,6 +409,44 @@ async def test_streaming_response_get(self, async_client: AsyncGcore) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_get_by_name(self, async_client: AsyncGcore) -> None: + network_mapping = await async_client.dns.network_mappings.get_by_name( + "name", + ) + assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) + + @parametrize + async def test_raw_response_get_by_name(self, async_client: AsyncGcore) -> None: + response = await async_client.dns.network_mappings.with_raw_response.get_by_name( + "name", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + network_mapping = await response.parse() + assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) + + @parametrize + async def test_streaming_response_get_by_name(self, async_client: AsyncGcore) -> None: + async with async_client.dns.network_mappings.with_streaming_response.get_by_name( + "name", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + network_mapping = await response.parse() + assert_matches_type(DNSNetworkMapping, network_mapping, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_get_by_name(self, async_client: AsyncGcore) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `name` but received ''"): + await async_client.dns.network_mappings.with_raw_response.get_by_name( + "", + ) + @parametrize async def test_method_import(self, async_client: AsyncGcore) -> None: network_mapping = await async_client.dns.network_mappings.import_() From c95a0b8b047bb8f7370934fe35576648163e3a3e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:06:15 +0000 Subject: [PATCH 06/19] feat(api): fix(cdn): harmonize pagination across CDN list endpoints Mark CDN resource rules, shields, and rule templates list endpoints with `paginated: false` to align with how other CDN endpoints handle pagination, since these return full arrays rather than paginated responses. --- .stats.yml | 2 +- src/gcore/resources/cdn/api.md | 6 +- .../resources/cdn/cdn_resources/rules.py | 80 +++++++++--------- src/gcore/resources/cdn/rule_templates.py | 76 ++++++++--------- src/gcore/resources/cdn/shields.py | 81 ++++++++++--------- src/gcore/types/cdn/shield_list_response.py | 37 ++++++++- .../cdn/cdn_resources/test_rules.py | 18 ++--- .../api_resources/cdn/test_rule_templates.py | 18 ++--- tests/api_resources/cdn/test_shields.py | 17 ++-- 9 files changed, 193 insertions(+), 142 deletions(-) diff --git a/.stats.yml b/.stats.yml index c73293bb..adfc2e6b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-2c51dec6cb0178c3e94e32aaf2ccb1627fdc815fa181a2c053861e1ccf1392bb.yml openapi_spec_hash: a763e3bba4d1827b1ebf28b59ceebfe4 -config_hash: 783d3945b23f1d5cf64f8fba8e44c9d7 +config_hash: a8091c31251bc8979abd6db1f2c88983 diff --git a/src/gcore/resources/cdn/api.md b/src/gcore/resources/cdn/api.md index 70a85773..002855e7 100644 --- a/src/gcore/resources/cdn/api.md +++ b/src/gcore/resources/cdn/api.md @@ -69,7 +69,7 @@ Methods: - client.cdn.cdn_resources.rules.create(resource_id, \*\*params) -> CDNResourceRule - client.cdn.cdn_resources.rules.update(rule_id, \*, resource_id, \*\*params) -> CDNResourceRule -- client.cdn.cdn_resources.rules.list(resource_id, \*\*params) -> SyncOffsetPage[CDNResourceRule] +- client.cdn.cdn_resources.rules.list(resource_id, \*\*params) -> CDNResourceRuleList - client.cdn.cdn_resources.rules.delete(rule_id, \*, resource_id) -> None - client.cdn.cdn_resources.rules.get(rule_id, \*, resource_id) -> CDNResourceRule - client.cdn.cdn_resources.rules.replace(rule_id, \*, resource_id, \*\*params) -> CDNResourceRule @@ -84,7 +84,7 @@ from gcore.types.cdn import ShieldListResponse Methods: -- client.cdn.shields.list(\*\*params) -> SyncOffsetPage[ShieldListResponse] +- client.cdn.shields.list(\*\*params) -> ShieldListResponse ## OriginGroups @@ -115,7 +115,7 @@ Methods: - client.cdn.rule_templates.create(\*\*params) -> RuleTemplate - client.cdn.rule_templates.update(rule_template_id, \*\*params) -> RuleTemplate -- client.cdn.rule_templates.list(\*\*params) -> SyncOffsetPage[RuleTemplate] +- client.cdn.rule_templates.list(\*\*params) -> RuleTemplateList - client.cdn.rule_templates.delete(rule_template_id) -> None - client.cdn.rule_templates.get(rule_template_id) -> RuleTemplate - client.cdn.rule_templates.replace(rule_template_id, \*\*params) -> RuleTemplate diff --git a/src/gcore/resources/cdn/cdn_resources/rules.py b/src/gcore/resources/cdn/cdn_resources/rules.py index 2fabd405..bb3ae8ed 100644 --- a/src/gcore/resources/cdn/cdn_resources/rules.py +++ b/src/gcore/resources/cdn/cdn_resources/rules.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Any, Optional, cast from typing_extensions import Literal import httpx @@ -17,10 +17,10 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from ....pagination import SyncOffsetPage, AsyncOffsetPage -from ...._base_client import AsyncPaginator, make_request_options +from ...._base_client import make_request_options from ....types.cdn.cdn_resources import rule_list_params, rule_create_params, rule_update_params, rule_replace_params from ....types.cdn.cdn_resources.cdn_resource_rule import CDNResourceRule +from ....types.cdn.cdn_resources.cdn_resource_rule_list import CDNResourceRuleList __all__ = ["RulesResource", "AsyncRulesResource"] @@ -272,7 +272,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SyncOffsetPage[CDNResourceRule]: + ) -> CDNResourceRuleList: """Get rules list Args: @@ -290,23 +290,27 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( - path_template("/cdn/resources/{resource_id}/rules", resource_id=resource_id), - page=SyncOffsetPage[CDNResourceRule], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "limit": limit, - "offset": offset, - }, - rule_list_params.RuleListParams, + return cast( + CDNResourceRuleList, + self._get( + path_template("/cdn/resources/{resource_id}/rules", resource_id=resource_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + }, + rule_list_params.RuleListParams, + ), ), + cast_to=cast( + Any, CDNResourceRuleList + ), # Union types cannot be passed in as arguments in the type system ), - model=CDNResourceRule, ) def delete( @@ -723,7 +727,7 @@ async def update( cast_to=CDNResourceRule, ) - def list( + async def list( self, resource_id: int, *, @@ -735,7 +739,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AsyncPaginator[CDNResourceRule, AsyncOffsetPage[CDNResourceRule]]: + ) -> CDNResourceRuleList: """Get rules list Args: @@ -753,23 +757,27 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( - path_template("/cdn/resources/{resource_id}/rules", resource_id=resource_id), - page=AsyncOffsetPage[CDNResourceRule], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "limit": limit, - "offset": offset, - }, - rule_list_params.RuleListParams, + return cast( + CDNResourceRuleList, + await self._get( + path_template("/cdn/resources/{resource_id}/rules", resource_id=resource_id), + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "limit": limit, + "offset": offset, + }, + rule_list_params.RuleListParams, + ), ), + cast_to=cast( + Any, CDNResourceRuleList + ), # Union types cannot be passed in as arguments in the type system ), - model=CDNResourceRule, ) async def delete( diff --git a/src/gcore/resources/cdn/rule_templates.py b/src/gcore/resources/cdn/rule_templates.py index 7080e040..86f670b3 100644 --- a/src/gcore/resources/cdn/rule_templates.py +++ b/src/gcore/resources/cdn/rule_templates.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Optional +from typing import Any, Optional, cast from typing_extensions import Literal import httpx @@ -23,9 +23,9 @@ rule_template_update_params, rule_template_replace_params, ) -from ...pagination import SyncOffsetPage, AsyncOffsetPage -from ..._base_client import AsyncPaginator, make_request_options +from ..._base_client import make_request_options from ...types.cdn.rule_template import RuleTemplate +from ...types.cdn.rule_template_list import RuleTemplateList __all__ = ["RuleTemplatesResource", "AsyncRuleTemplatesResource"] @@ -242,7 +242,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SyncOffsetPage[RuleTemplate]: + ) -> RuleTemplateList: """ Get rule templates list @@ -259,23 +259,25 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( - "/cdn/resources/rule_templates", - page=SyncOffsetPage[RuleTemplate], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "limit": limit, - "offset": offset, - }, - rule_template_list_params.RuleTemplateListParams, + return cast( + RuleTemplateList, + self._get( + "/cdn/resources/rule_templates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + }, + rule_template_list_params.RuleTemplateListParams, + ), ), + cast_to=cast(Any, RuleTemplateList), # Union types cannot be passed in as arguments in the type system ), - model=RuleTemplate, ) def delete( @@ -632,7 +634,7 @@ async def update( cast_to=RuleTemplate, ) - def list( + async def list( self, *, limit: int | Omit = omit, @@ -643,7 +645,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AsyncPaginator[RuleTemplate, AsyncOffsetPage[RuleTemplate]]: + ) -> RuleTemplateList: """ Get rule templates list @@ -660,23 +662,25 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( - "/cdn/resources/rule_templates", - page=AsyncOffsetPage[RuleTemplate], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "limit": limit, - "offset": offset, - }, - rule_template_list_params.RuleTemplateListParams, + return cast( + RuleTemplateList, + await self._get( + "/cdn/resources/rule_templates", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "limit": limit, + "offset": offset, + }, + rule_template_list_params.RuleTemplateListParams, + ), ), + cast_to=cast(Any, RuleTemplateList), # Union types cannot be passed in as arguments in the type system ), - model=RuleTemplate, ) async def delete( diff --git a/src/gcore/resources/cdn/shields.py b/src/gcore/resources/cdn/shields.py index 8eb3ed5f..51807bf0 100644 --- a/src/gcore/resources/cdn/shields.py +++ b/src/gcore/resources/cdn/shields.py @@ -2,10 +2,12 @@ from __future__ import annotations +from typing import Any, cast + import httpx from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from ..._utils import maybe_transform +from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -15,8 +17,7 @@ async_to_streamed_response_wrapper, ) from ...types.cdn import shield_list_params -from ...pagination import SyncOffsetPage, AsyncOffsetPage -from ..._base_client import AsyncPaginator, make_request_options +from ..._base_client import make_request_options from ...types.cdn.shield_list_response import ShieldListResponse __all__ = ["ShieldsResource", "AsyncShieldsResource"] @@ -53,7 +54,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> SyncOffsetPage[ShieldListResponse]: + ) -> ShieldListResponse: """ Get information about all origin shielding locations available in the account. @@ -70,23 +71,27 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( - "/cdn/shieldingpop_v2", - page=SyncOffsetPage[ShieldListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "limit": limit, - "offset": offset, - }, - shield_list_params.ShieldListParams, + return cast( + ShieldListResponse, + self._get( + "/cdn/shieldingpop_v2", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + }, + shield_list_params.ShieldListParams, + ), ), + cast_to=cast( + Any, ShieldListResponse + ), # Union types cannot be passed in as arguments in the type system ), - model=ShieldListResponse, ) @@ -110,7 +115,7 @@ def with_streaming_response(self) -> AsyncShieldsResourceWithStreamingResponse: """ return AsyncShieldsResourceWithStreamingResponse(self) - def list( + async def list( self, *, limit: int | Omit = omit, @@ -121,7 +126,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AsyncPaginator[ShieldListResponse, AsyncOffsetPage[ShieldListResponse]]: + ) -> ShieldListResponse: """ Get information about all origin shielding locations available in the account. @@ -138,23 +143,27 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get_api_list( - "/cdn/shieldingpop_v2", - page=AsyncOffsetPage[ShieldListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "limit": limit, - "offset": offset, - }, - shield_list_params.ShieldListParams, + return cast( + ShieldListResponse, + await self._get( + "/cdn/shieldingpop_v2", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "limit": limit, + "offset": offset, + }, + shield_list_params.ShieldListParams, + ), ), + cast_to=cast( + Any, ShieldListResponse + ), # Union types cannot be passed in as arguments in the type system ), - model=ShieldListResponse, ) diff --git a/src/gcore/types/cdn/shield_list_response.py b/src/gcore/types/cdn/shield_list_response.py index 6956cf1e..458a400c 100644 --- a/src/gcore/types/cdn/shield_list_response.py +++ b/src/gcore/types/cdn/shield_list_response.py @@ -1,13 +1,14 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Optional +from typing import List, Union, Optional +from typing_extensions import TypeAlias from ..._models import BaseModel -__all__ = ["ShieldListResponse"] +__all__ = ["ShieldListResponse", "PlainList", "PaginatedList", "PaginatedListResult"] -class ShieldListResponse(BaseModel): +class PlainList(BaseModel): id: Optional[int] = None """Origin shielding location ID.""" @@ -19,3 +20,33 @@ class ShieldListResponse(BaseModel): datacenter: Optional[str] = None """Name of origin shielding location datacenter.""" + + +class PaginatedListResult(BaseModel): + id: Optional[int] = None + """Origin shielding location ID.""" + + city: Optional[str] = None + """City of origin shielding location.""" + + country: Optional[str] = None + """Country of origin shielding location.""" + + datacenter: Optional[str] = None + """Name of origin shielding location datacenter.""" + + +class PaginatedList(BaseModel): + count: int + """Total number of items.""" + + next: Optional[str] = None + """URL to the next page of results. Null if current page is the last one.""" + + previous: Optional[str] = None + """URL to the previous page of results. Null if current page is the first one.""" + + results: List[PaginatedListResult] + + +ShieldListResponse: TypeAlias = Union[List[PlainList], PaginatedList] diff --git a/tests/api_resources/cdn/cdn_resources/test_rules.py b/tests/api_resources/cdn/cdn_resources/test_rules.py index 0fb4027d..e7a410e0 100644 --- a/tests/api_resources/cdn/cdn_resources/test_rules.py +++ b/tests/api_resources/cdn/cdn_resources/test_rules.py @@ -9,9 +9,9 @@ from gcore import Gcore, AsyncGcore from tests.utils import assert_matches_type -from gcore.pagination import SyncOffsetPage, AsyncOffsetPage from gcore.types.cdn.cdn_resources import ( CDNResourceRule, + CDNResourceRuleList, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -658,7 +658,7 @@ def test_method_list(self, client: Gcore) -> None: rule = client.cdn.cdn_resources.rules.list( resource_id=0, ) - assert_matches_type(SyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: @@ -667,7 +667,7 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: limit=1, offset=0, ) - assert_matches_type(SyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) @parametrize def test_raw_response_list(self, client: Gcore) -> None: @@ -678,7 +678,7 @@ def test_raw_response_list(self, client: Gcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule = response.parse() - assert_matches_type(SyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) @parametrize def test_streaming_response_list(self, client: Gcore) -> None: @@ -689,7 +689,7 @@ def test_streaming_response_list(self, client: Gcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule = response.parse() - assert_matches_type(SyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) assert cast(Any, response.is_closed) is True @@ -1725,7 +1725,7 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: rule = await async_client.cdn.cdn_resources.rules.list( resource_id=0, ) - assert_matches_type(AsyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: @@ -1734,7 +1734,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No limit=1, offset=0, ) - assert_matches_type(AsyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @@ -1745,7 +1745,7 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule = await response.parse() - assert_matches_type(AsyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: @@ -1756,7 +1756,7 @@ async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule = await response.parse() - assert_matches_type(AsyncOffsetPage[CDNResourceRule], rule, path=["response"]) + assert_matches_type(CDNResourceRuleList, rule, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/cdn/test_rule_templates.py b/tests/api_resources/cdn/test_rule_templates.py index 0457f565..b81bbb02 100644 --- a/tests/api_resources/cdn/test_rule_templates.py +++ b/tests/api_resources/cdn/test_rule_templates.py @@ -11,8 +11,8 @@ from tests.utils import assert_matches_type from gcore.types.cdn import ( RuleTemplate, + RuleTemplateList, ) -from gcore.pagination import SyncOffsetPage, AsyncOffsetPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -641,7 +641,7 @@ def test_streaming_response_update(self, client: Gcore) -> None: @parametrize def test_method_list(self, client: Gcore) -> None: rule_template = client.cdn.rule_templates.list() - assert_matches_type(SyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: @@ -649,7 +649,7 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: limit=1, offset=0, ) - assert_matches_type(SyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) @parametrize def test_raw_response_list(self, client: Gcore) -> None: @@ -658,7 +658,7 @@ def test_raw_response_list(self, client: Gcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule_template = response.parse() - assert_matches_type(SyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) @parametrize def test_streaming_response_list(self, client: Gcore) -> None: @@ -667,7 +667,7 @@ def test_streaming_response_list(self, client: Gcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule_template = response.parse() - assert_matches_type(SyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) assert cast(Any, response.is_closed) is True @@ -1674,7 +1674,7 @@ async def test_streaming_response_update(self, async_client: AsyncGcore) -> None @parametrize async def test_method_list(self, async_client: AsyncGcore) -> None: rule_template = await async_client.cdn.rule_templates.list() - assert_matches_type(AsyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: @@ -1682,7 +1682,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No limit=1, offset=0, ) - assert_matches_type(AsyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @@ -1691,7 +1691,7 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule_template = await response.parse() - assert_matches_type(AsyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: @@ -1700,7 +1700,7 @@ async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" rule_template = await response.parse() - assert_matches_type(AsyncOffsetPage[RuleTemplate], rule_template, path=["response"]) + assert_matches_type(RuleTemplateList, rule_template, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/cdn/test_shields.py b/tests/api_resources/cdn/test_shields.py index f175c08a..5444a0f1 100644 --- a/tests/api_resources/cdn/test_shields.py +++ b/tests/api_resources/cdn/test_shields.py @@ -10,7 +10,6 @@ from gcore import Gcore, AsyncGcore from tests.utils import assert_matches_type from gcore.types.cdn import ShieldListResponse -from gcore.pagination import SyncOffsetPage, AsyncOffsetPage base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,7 +20,7 @@ class TestShields: @parametrize def test_method_list(self, client: Gcore) -> None: shield = client.cdn.shields.list() - assert_matches_type(SyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: @@ -29,7 +28,7 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: limit=1, offset=0, ) - assert_matches_type(SyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) @parametrize def test_raw_response_list(self, client: Gcore) -> None: @@ -38,7 +37,7 @@ def test_raw_response_list(self, client: Gcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" shield = response.parse() - assert_matches_type(SyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) @parametrize def test_streaming_response_list(self, client: Gcore) -> None: @@ -47,7 +46,7 @@ def test_streaming_response_list(self, client: Gcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" shield = response.parse() - assert_matches_type(SyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) assert cast(Any, response.is_closed) is True @@ -60,7 +59,7 @@ class TestAsyncShields: @parametrize async def test_method_list(self, async_client: AsyncGcore) -> None: shield = await async_client.cdn.shields.list() - assert_matches_type(AsyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: @@ -68,7 +67,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No limit=1, offset=0, ) - assert_matches_type(AsyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @@ -77,7 +76,7 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" shield = await response.parse() - assert_matches_type(AsyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: @@ -86,6 +85,6 @@ async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" shield = await response.parse() - assert_matches_type(AsyncOffsetPage[ShieldListResponse], shield, path=["response"]) + assert_matches_type(ShieldListResponse, shield, path=["response"]) assert cast(Any, response.is_closed) is True From 0971eaa023789ebaf5baeae39f4977cba3cbab1d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 16:11:21 +0000 Subject: [PATCH 07/19] fix(client): preserve hardcoded query params when merging with user params --- src/gcore/_base_client.py | 4 ++++ tests/test_client.py | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/gcore/_base_client.py b/src/gcore/_base_client.py index e797b152..e3e40a42 100644 --- a/src/gcore/_base_client.py +++ b/src/gcore/_base_client.py @@ -540,6 +540,10 @@ def _build_request( files = cast(HttpxRequestFiles, ForceMultipartDict()) prepared_url = self._prepare_url(options.url) + # preserve hard-coded query params from the url + if params and prepared_url.query: + params = {**dict(prepared_url.params.items()), **params} + prepared_url = prepared_url.copy_with(raw_path=prepared_url.raw_path.split(b"?", 1)[0]) if "_" in prepared_url.host: # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} diff --git a/tests/test_client.py b/tests/test_client.py index 9a14904e..b27528a8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -427,6 +427,30 @@ def test_default_query_option(self) -> None: client.close() + def test_hardcoded_query_params_in_url(self, client: Gcore) -> None: + request = client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + def test_cloud_project_id_client_params(self, client: Gcore) -> None: # Test with base client (no custom params) with pytest.raises(ValueError, match="Missing cloud_project_id argument;"): @@ -1338,6 +1362,30 @@ async def test_default_query_option(self) -> None: await client.close() + async def test_hardcoded_query_params_in_url(self, async_client: AsyncGcore) -> None: + request = async_client._build_request(FinalRequestOptions(method="get", url="/foo?beta=true")) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/foo?beta=true", + params={"limit": "10", "page": "abc"}, + ) + ) + url = httpx.URL(request.url) + assert dict(url.params) == {"beta": "true", "limit": "10", "page": "abc"} + + request = async_client._build_request( + FinalRequestOptions( + method="get", + url="/files/a%2Fb?beta=true", + params={"limit": "10"}, + ) + ) + assert request.url.raw_path == b"/files/a%2Fb?beta=true&limit=10" + async def test_cloud_project_id_client_params(self, async_client: AsyncGcore) -> None: # Test with base client (no custom params) with pytest.raises(ValueError, match="Missing cloud_project_id argument;"): From 01be2f5ba1c94daeff5e61a4672b9ff8b0bac7cc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 08:36:19 +0000 Subject: [PATCH 08/19] feat(api): aggregated API specs update --- .stats.yml | 4 +- src/gcore/resources/cloud/api.md | 4 +- .../resources/cloud/load_balancers/flavors.py | 30 ++++- .../load_balancers/l7_policies/l7_policies.py | 40 ++++++- .../cloud/load_balancers/l7_policies/rules.py | 40 ++++++- .../cloud/load_balancers/listeners.py | 18 +++ .../cloud/load_balancers/load_balancers.py | 26 ++--- .../cloud/load_balancers/pools/pools.py | 18 +++ .../resources/cloud/security_groups/rules.py | 106 +++++++++--------- .../cloud/load_balancer_create_params.py | 12 +- .../types/cloud/load_balancers/__init__.py | 1 + .../load_balancers/flavor_list_params.py | 9 ++ .../load_balancers/l7_policies/__init__.py | 1 + .../l7_policies/rule_list_params.py | 24 ++++ .../load_balancers/l7_policy_list_params.py | 24 ++++ .../load_balancers/listener_list_params.py | 9 ++ .../cloud/load_balancers/pool_list_params.py | 9 ++ .../cloud/security_group_create_params.py | 53 ++++----- .../cloud/security_group_update_params.py | 53 ++++----- .../security_groups/rule_create_params.py | 53 ++++----- .../load_balancers/l7_policies/test_rules.py | 22 ++++ .../cloud/load_balancers/test_flavors.py | 4 + .../cloud/load_balancers/test_l7_policies.py | 20 ++++ .../cloud/load_balancers/test_listeners.py | 4 + .../cloud/load_balancers/test_pools.py | 4 + .../cloud/test_load_balancers.py | 12 +- 26 files changed, 427 insertions(+), 173 deletions(-) create mode 100644 src/gcore/types/cloud/load_balancers/l7_policies/rule_list_params.py create mode 100644 src/gcore/types/cloud/load_balancers/l7_policy_list_params.py diff --git a/.stats.yml b/.stats.yml index adfc2e6b..705316e9 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-2c51dec6cb0178c3e94e32aaf2ccb1627fdc815fa181a2c053861e1ccf1392bb.yml -openapi_spec_hash: a763e3bba4d1827b1ebf28b59ceebfe4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-d22228888cb7ed9c880f41e9642e4e3f406c3401d5ef7ba8cb3674f12864682c.yml +openapi_spec_hash: a150cff71663ca3fc455be832781e306 config_hash: a8091c31251bc8979abd6db1f2c88983 diff --git a/src/gcore/resources/cloud/api.md b/src/gcore/resources/cloud/api.md index 6f43d248..17de3c77 100644 --- a/src/gcore/resources/cloud/api.md +++ b/src/gcore/resources/cloud/api.md @@ -235,7 +235,7 @@ Methods: - client.cloud.load_balancers.l7_policies.create(\*, project_id, region_id, \*\*params) -> TaskIDList - client.cloud.load_balancers.l7_policies.update(l7policy_id, \*, project_id, region_id, \*\*params) -> TaskIDList -- client.cloud.load_balancers.l7_policies.list(\*, project_id, region_id) -> LoadBalancerL7PolicyList +- client.cloud.load_balancers.l7_policies.list(\*, project_id, region_id, \*\*params) -> LoadBalancerL7PolicyList - client.cloud.load_balancers.l7_policies.delete(l7policy_id, \*, project_id, region_id) -> TaskIDList - client.cloud.load_balancers.l7_policies.get(l7policy_id, \*, project_id, region_id) -> LoadBalancerL7Policy @@ -244,7 +244,7 @@ Methods: Methods: - client.cloud.load_balancers.l7_policies.rules.create(l7policy_id, \*, project_id, region_id, \*\*params) -> TaskIDList -- client.cloud.load_balancers.l7_policies.rules.list(l7policy_id, \*, project_id, region_id) -> LoadBalancerL7RuleList +- client.cloud.load_balancers.l7_policies.rules.list(l7policy_id, \*, project_id, region_id, \*\*params) -> LoadBalancerL7RuleList - client.cloud.load_balancers.l7_policies.rules.delete(l7rule_id, \*, project_id, region_id, l7policy_id) -> TaskIDList - client.cloud.load_balancers.l7_policies.rules.get(l7rule_id, \*, project_id, region_id, l7policy_id) -> LoadBalancerL7Rule - client.cloud.load_balancers.l7_policies.rules.replace(l7rule_id, \*, project_id, region_id, l7policy_id, \*\*params) -> TaskIDList diff --git a/src/gcore/resources/cloud/load_balancers/flavors.py b/src/gcore/resources/cloud/load_balancers/flavors.py index 27e43c14..4f1c4f04 100644 --- a/src/gcore/resources/cloud/load_balancers/flavors.py +++ b/src/gcore/resources/cloud/load_balancers/flavors.py @@ -47,6 +47,8 @@ def list( project_id: int | None = None, region_id: int | None = None, include_prices: bool | Omit = omit, + limit: int | Omit = omit, + offset: 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, @@ -67,6 +69,11 @@ def list( include_prices: Set to true if the response should include flavor prices + limit: Optional. Limit the number of returned items + + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -86,7 +93,14 @@ def list( extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=maybe_transform({"include_prices": include_prices}, flavor_list_params.FlavorListParams), + query=maybe_transform( + { + "include_prices": include_prices, + "limit": limit, + "offset": offset, + }, + flavor_list_params.FlavorListParams, + ), ), cast_to=LoadBalancerFlavorList, ) @@ -118,6 +132,8 @@ async def list( project_id: int | None = None, region_id: int | None = None, include_prices: bool | Omit = omit, + limit: int | Omit = omit, + offset: 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, @@ -138,6 +154,11 @@ async def list( include_prices: Set to true if the response should include flavor prices + limit: Optional. Limit the number of returned items + + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -158,7 +179,12 @@ async def list( extra_body=extra_body, timeout=timeout, query=await async_maybe_transform( - {"include_prices": include_prices}, flavor_list_params.FlavorListParams + { + "include_prices": include_prices, + "limit": limit, + "offset": offset, + }, + flavor_list_params.FlavorListParams, ), ), cast_to=LoadBalancerFlavorList, diff --git a/src/gcore/resources/cloud/load_balancers/l7_policies/l7_policies.py b/src/gcore/resources/cloud/load_balancers/l7_policies/l7_policies.py index b9243fe3..b9674fad 100644 --- a/src/gcore/resources/cloud/load_balancers/l7_policies/l7_policies.py +++ b/src/gcore/resources/cloud/load_balancers/l7_policies/l7_policies.py @@ -26,7 +26,7 @@ ) from ....._base_client import make_request_options from .....types.cloud.task_id_list import TaskIDList -from .....types.cloud.load_balancers import l7_policy_create_params, l7_policy_update_params +from .....types.cloud.load_balancers import l7_policy_list_params, l7_policy_create_params, l7_policy_update_params from .....types.cloud.load_balancer_l7_policy import LoadBalancerL7Policy from .....types.cloud.load_balancer_l7_policy_list import LoadBalancerL7PolicyList @@ -584,6 +584,8 @@ def list( *, project_id: int | None = None, region_id: int | None = None, + limit: int | Omit = omit, + offset: 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, @@ -599,6 +601,11 @@ def list( region_id: Region ID + limit: Optional. Limit the number of returned items + + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -614,7 +621,17 @@ def list( return self._get( path_template("/cloud/v1/l7policies/{project_id}/{region_id}", project_id=project_id, region_id=region_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + }, + l7_policy_list_params.L7PolicyListParams, + ), ), cast_to=LoadBalancerL7PolicyList, ) @@ -1613,6 +1630,8 @@ async def list( *, project_id: int | None = None, region_id: int | None = None, + limit: int | Omit = omit, + offset: 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, @@ -1628,6 +1647,11 @@ async def list( region_id: Region ID + limit: Optional. Limit the number of returned items + + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1643,7 +1667,17 @@ async def list( return await self._get( path_template("/cloud/v1/l7policies/{project_id}/{region_id}", project_id=project_id, region_id=region_id), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "limit": limit, + "offset": offset, + }, + l7_policy_list_params.L7PolicyListParams, + ), ), cast_to=LoadBalancerL7PolicyList, ) diff --git a/src/gcore/resources/cloud/load_balancers/l7_policies/rules.py b/src/gcore/resources/cloud/load_balancers/l7_policies/rules.py index 901baf03..1267a937 100644 --- a/src/gcore/resources/cloud/load_balancers/l7_policies/rules.py +++ b/src/gcore/resources/cloud/load_balancers/l7_policies/rules.py @@ -20,7 +20,7 @@ from .....types.cloud.task_id_list import TaskIDList from .....types.cloud.load_balancer_l7_rule import LoadBalancerL7Rule from .....types.cloud.load_balancer_l7_rule_list import LoadBalancerL7RuleList -from .....types.cloud.load_balancers.l7_policies import rule_create_params, rule_replace_params +from .....types.cloud.load_balancers.l7_policies import rule_list_params, rule_create_params, rule_replace_params __all__ = ["RulesResource", "AsyncRulesResource"] @@ -139,6 +139,8 @@ def list( *, project_id: int | None = None, region_id: int | None = None, + limit: int | Omit = omit, + offset: 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, @@ -156,6 +158,11 @@ def list( l7policy_id: L7 policy ID + limit: Optional. Limit the number of returned items + + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -178,7 +185,17 @@ def list( l7policy_id=l7policy_id, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "offset": offset, + }, + rule_list_params.RuleListParams, + ), ), cast_to=LoadBalancerL7RuleList, ) @@ -674,6 +691,8 @@ async def list( *, project_id: int | None = None, region_id: int | None = None, + limit: int | Omit = omit, + offset: 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, @@ -691,6 +710,11 @@ async def list( l7policy_id: L7 policy ID + limit: Optional. Limit the number of returned items + + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -713,7 +737,17 @@ async def list( l7policy_id=l7policy_id, ), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "limit": limit, + "offset": offset, + }, + rule_list_params.RuleListParams, + ), ), cast_to=LoadBalancerL7RuleList, ) diff --git a/src/gcore/resources/cloud/load_balancers/listeners.py b/src/gcore/resources/cloud/load_balancers/listeners.py index cd6c6dcc..5de5286e 100644 --- a/src/gcore/resources/cloud/load_balancers/listeners.py +++ b/src/gcore/resources/cloud/load_balancers/listeners.py @@ -272,7 +272,9 @@ def list( *, project_id: int | None = None, region_id: int | None = None, + limit: int | Omit = omit, load_balancer_id: str | Omit = omit, + offset: int | Omit = omit, show_stats: bool | 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. @@ -289,8 +291,13 @@ def list( region_id: Region ID + limit: Optional. Limit the number of returned items + load_balancer_id: Load Balancer ID + offset: Optional. Offset value is used to exclude the first set of records from the + result + show_stats: Show stats extra_headers: Send extra headers @@ -314,7 +321,9 @@ def list( timeout=timeout, query=maybe_transform( { + "limit": limit, "load_balancer_id": load_balancer_id, + "offset": offset, "show_stats": show_stats, }, listener_list_params.ListenerListParams, @@ -845,7 +854,9 @@ async def list( *, project_id: int | None = None, region_id: int | None = None, + limit: int | Omit = omit, load_balancer_id: str | Omit = omit, + offset: int | Omit = omit, show_stats: bool | 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. @@ -862,8 +873,13 @@ async def list( region_id: Region ID + limit: Optional. Limit the number of returned items + load_balancer_id: Load Balancer ID + offset: Optional. Offset value is used to exclude the first set of records from the + result + show_stats: Show stats extra_headers: Send extra headers @@ -887,7 +903,9 @@ async def list( timeout=timeout, query=await async_maybe_transform( { + "limit": limit, "load_balancer_id": load_balancer_id, + "offset": offset, "show_stats": show_stats, }, listener_list_params.ListenerListParams, diff --git a/src/gcore/resources/cloud/load_balancers/load_balancers.py b/src/gcore/resources/cloud/load_balancers/load_balancers.py index c9dd19be..c9957907 100644 --- a/src/gcore/resources/cloud/load_balancers/load_balancers.py +++ b/src/gcore/resources/cloud/load_balancers/load_balancers.py @@ -146,12 +146,11 @@ def create( *, project_id: int | None = None, region_id: int | None = None, + name: str, flavor: str | Omit = omit, floating_ip: load_balancer_create_params.FloatingIP | Omit = omit, listeners: Iterable[load_balancer_create_params.Listener] | Omit = omit, logging: load_balancer_create_params.Logging | Omit = omit, - name: str | Omit = omit, - name_template: str | Omit = omit, preferred_connectivity: LoadBalancerMemberConnectivity | Omit = omit, tags: Dict[str, str] | Omit = omit, vip_ip_family: InterfaceIPFamily | Omit = omit, @@ -173,6 +172,8 @@ def create( region_id: Region ID + name: Load balancer name. + flavor: Load balancer flavor name floating_ip: Floating IP configuration for assignment @@ -182,11 +183,6 @@ def create( logging: Logging configuration - name: Load balancer name. Either `name` or `name_template` should be specified. - - name_template: Load balancer name which will be changed by template. Either `name` or - `name_template` should be specified. - preferred_connectivity: Preferred option to establish connectivity between load balancer and its pools members. L2 provides best performance, L3 provides less IPs usage. It is taking effect only if `instance_id` + `ip_address` is provided, not `subnet_id` + @@ -230,12 +226,11 @@ def create( ), body=maybe_transform( { + "name": name, "flavor": flavor, "floating_ip": floating_ip, "listeners": listeners, "logging": logging, - "name": name, - "name_template": name_template, "preferred_connectivity": preferred_connectivity, "tags": tags, "vip_ip_family": vip_ip_family, @@ -937,12 +932,11 @@ async def create( *, project_id: int | None = None, region_id: int | None = None, + name: str, flavor: str | Omit = omit, floating_ip: load_balancer_create_params.FloatingIP | Omit = omit, listeners: Iterable[load_balancer_create_params.Listener] | Omit = omit, logging: load_balancer_create_params.Logging | Omit = omit, - name: str | Omit = omit, - name_template: str | Omit = omit, preferred_connectivity: LoadBalancerMemberConnectivity | Omit = omit, tags: Dict[str, str] | Omit = omit, vip_ip_family: InterfaceIPFamily | Omit = omit, @@ -964,6 +958,8 @@ async def create( region_id: Region ID + name: Load balancer name. + flavor: Load balancer flavor name floating_ip: Floating IP configuration for assignment @@ -973,11 +969,6 @@ async def create( logging: Logging configuration - name: Load balancer name. Either `name` or `name_template` should be specified. - - name_template: Load balancer name which will be changed by template. Either `name` or - `name_template` should be specified. - preferred_connectivity: Preferred option to establish connectivity between load balancer and its pools members. L2 provides best performance, L3 provides less IPs usage. It is taking effect only if `instance_id` + `ip_address` is provided, not `subnet_id` + @@ -1021,12 +1012,11 @@ async def create( ), body=await async_maybe_transform( { + "name": name, "flavor": flavor, "floating_ip": floating_ip, "listeners": listeners, "logging": logging, - "name": name, - "name_template": name_template, "preferred_connectivity": preferred_connectivity, "tags": tags, "vip_ip_family": vip_ip_family, diff --git a/src/gcore/resources/cloud/load_balancers/pools/pools.py b/src/gcore/resources/cloud/load_balancers/pools/pools.py index ea10ebba..330079da 100644 --- a/src/gcore/resources/cloud/load_balancers/pools/pools.py +++ b/src/gcore/resources/cloud/load_balancers/pools/pools.py @@ -315,8 +315,10 @@ def list( project_id: int | None = None, region_id: int | None = None, details: bool | Omit = omit, + limit: int | Omit = omit, listener_id: str | Omit = omit, load_balancer_id: str | Omit = omit, + offset: 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, @@ -334,10 +336,15 @@ def list( details: Show members and Health Monitor details + limit: Optional. Limit the number of returned items + listener_id: Listener ID load_balancer_id: Load Balancer ID + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -360,8 +367,10 @@ def list( query=maybe_transform( { "details": details, + "limit": limit, "listener_id": listener_id, "load_balancer_id": load_balancer_id, + "offset": offset, }, pool_list_params.PoolListParams, ), @@ -912,8 +921,10 @@ async def list( project_id: int | None = None, region_id: int | None = None, details: bool | Omit = omit, + limit: int | Omit = omit, listener_id: str | Omit = omit, load_balancer_id: str | Omit = omit, + offset: 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, @@ -931,10 +942,15 @@ async def list( details: Show members and Health Monitor details + limit: Optional. Limit the number of returned items + listener_id: Listener ID load_balancer_id: Load Balancer ID + offset: Optional. Offset value is used to exclude the first set of records from the + result + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -957,8 +973,10 @@ async def list( query=await async_maybe_transform( { "details": details, + "limit": limit, "listener_id": listener_id, "load_balancer_id": load_balancer_id, + "offset": offset, }, pool_list_params.PoolListParams, ), diff --git a/src/gcore/resources/cloud/security_groups/rules.py b/src/gcore/resources/cloud/security_groups/rules.py index 9fba7c36..8260bd42 100644 --- a/src/gcore/resources/cloud/security_groups/rules.py +++ b/src/gcore/resources/cloud/security_groups/rules.py @@ -61,31 +61,32 @@ def create( ethertype: Literal["IPv4", "IPv6"] | Omit = omit, port_range_max: Optional[int] | Omit = omit, port_range_min: Optional[int] | Omit = omit, - protocol: Literal[ - "ah", - "any", - "dccp", - "egp", - "esp", - "gre", - "icmp", - "igmp", - "ipencap", - "ipip", - "ipv6-encap", - "ipv6-frag", - "ipv6-icmp", - "ipv6-nonxt", - "ipv6-opts", - "ipv6-route", - "ospf", - "pgm", - "rsvp", - "sctp", - "tcp", - "udp", - "udplite", - "vrrp", + protocol: Optional[ + Literal[ + "ah", + "dccp", + "egp", + "esp", + "gre", + "icmp", + "igmp", + "ipencap", + "ipip", + "ipv6-encap", + "ipv6-frag", + "ipv6-icmp", + "ipv6-nonxt", + "ipv6-opts", + "ipv6-route", + "ospf", + "pgm", + "rsvp", + "sctp", + "tcp", + "udp", + "udplite", + "vrrp", + ] ] | Omit = omit, remote_group_id: str | Omit = omit, @@ -119,7 +120,7 @@ def create( port_range_min: The minimum port number in the range that is matched by the security group rule - protocol: Protocol + protocol: V2 protocol enum without 'any'. Use null for all protocols instead. remote_group_id: The remote group UUID to associate with this security group @@ -522,31 +523,32 @@ async def create( ethertype: Literal["IPv4", "IPv6"] | Omit = omit, port_range_max: Optional[int] | Omit = omit, port_range_min: Optional[int] | Omit = omit, - protocol: Literal[ - "ah", - "any", - "dccp", - "egp", - "esp", - "gre", - "icmp", - "igmp", - "ipencap", - "ipip", - "ipv6-encap", - "ipv6-frag", - "ipv6-icmp", - "ipv6-nonxt", - "ipv6-opts", - "ipv6-route", - "ospf", - "pgm", - "rsvp", - "sctp", - "tcp", - "udp", - "udplite", - "vrrp", + protocol: Optional[ + Literal[ + "ah", + "dccp", + "egp", + "esp", + "gre", + "icmp", + "igmp", + "ipencap", + "ipip", + "ipv6-encap", + "ipv6-frag", + "ipv6-icmp", + "ipv6-nonxt", + "ipv6-opts", + "ipv6-route", + "ospf", + "pgm", + "rsvp", + "sctp", + "tcp", + "udp", + "udplite", + "vrrp", + ] ] | Omit = omit, remote_group_id: str | Omit = omit, @@ -580,7 +582,7 @@ async def create( port_range_min: The minimum port number in the range that is matched by the security group rule - protocol: Protocol + protocol: V2 protocol enum without 'any'. Use null for all protocols instead. remote_group_id: The remote group UUID to associate with this security group diff --git a/src/gcore/types/cloud/load_balancer_create_params.py b/src/gcore/types/cloud/load_balancer_create_params.py index b1fe2885..24e0724b 100644 --- a/src/gcore/types/cloud/load_balancer_create_params.py +++ b/src/gcore/types/cloud/load_balancer_create_params.py @@ -38,6 +38,9 @@ class LoadBalancerCreateParams(TypedDict, total=False): region_id: int """Region ID""" + name: Required[str] + """Load balancer name.""" + flavor: str """Load balancer flavor name""" @@ -53,15 +56,6 @@ class LoadBalancerCreateParams(TypedDict, total=False): logging: Logging """Logging configuration""" - name: str - """Load balancer name. Either `name` or `name_template` should be specified.""" - - name_template: str - """Load balancer name which will be changed by template. - - Either `name` or `name_template` should be specified. - """ - preferred_connectivity: LoadBalancerMemberConnectivity """ Preferred option to establish connectivity between load balancer and its pools diff --git a/src/gcore/types/cloud/load_balancers/__init__.py b/src/gcore/types/cloud/load_balancers/__init__.py index cfacdce7..7a7eddaf 100644 --- a/src/gcore/types/cloud/load_balancers/__init__.py +++ b/src/gcore/types/cloud/load_balancers/__init__.py @@ -9,6 +9,7 @@ from .pool_update_params import PoolUpdateParams as PoolUpdateParams from .listener_get_params import ListenerGetParams as ListenerGetParams from .listener_list_params import ListenerListParams as ListenerListParams +from .l7_policy_list_params import L7PolicyListParams as L7PolicyListParams from .listener_create_params import ListenerCreateParams as ListenerCreateParams from .listener_delete_params import ListenerDeleteParams as ListenerDeleteParams from .listener_update_params import ListenerUpdateParams as ListenerUpdateParams diff --git a/src/gcore/types/cloud/load_balancers/flavor_list_params.py b/src/gcore/types/cloud/load_balancers/flavor_list_params.py index af622cd6..ef732d4e 100644 --- a/src/gcore/types/cloud/load_balancers/flavor_list_params.py +++ b/src/gcore/types/cloud/load_balancers/flavor_list_params.py @@ -16,3 +16,12 @@ class FlavorListParams(TypedDict, total=False): include_prices: bool """Set to true if the response should include flavor prices""" + + limit: int + """Optional. Limit the number of returned items""" + + offset: int + """Optional. + + Offset value is used to exclude the first set of records from the result + """ diff --git a/src/gcore/types/cloud/load_balancers/l7_policies/__init__.py b/src/gcore/types/cloud/load_balancers/l7_policies/__init__.py index 832f1d77..91199151 100644 --- a/src/gcore/types/cloud/load_balancers/l7_policies/__init__.py +++ b/src/gcore/types/cloud/load_balancers/l7_policies/__init__.py @@ -2,5 +2,6 @@ from __future__ import annotations +from .rule_list_params import RuleListParams as RuleListParams from .rule_create_params import RuleCreateParams as RuleCreateParams from .rule_replace_params import RuleReplaceParams as RuleReplaceParams diff --git a/src/gcore/types/cloud/load_balancers/l7_policies/rule_list_params.py b/src/gcore/types/cloud/load_balancers/l7_policies/rule_list_params.py new file mode 100644 index 00000000..f4ccd8e0 --- /dev/null +++ b/src/gcore/types/cloud/load_balancers/l7_policies/rule_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["RuleListParams"] + + +class RuleListParams(TypedDict, total=False): + project_id: int + """Project ID""" + + region_id: int + """Region ID""" + + limit: int + """Optional. Limit the number of returned items""" + + offset: int + """Optional. + + Offset value is used to exclude the first set of records from the result + """ diff --git a/src/gcore/types/cloud/load_balancers/l7_policy_list_params.py b/src/gcore/types/cloud/load_balancers/l7_policy_list_params.py new file mode 100644 index 00000000..2f63dec0 --- /dev/null +++ b/src/gcore/types/cloud/load_balancers/l7_policy_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import TypedDict + +__all__ = ["L7PolicyListParams"] + + +class L7PolicyListParams(TypedDict, total=False): + project_id: int + """Project ID""" + + region_id: int + """Region ID""" + + limit: int + """Optional. Limit the number of returned items""" + + offset: int + """Optional. + + Offset value is used to exclude the first set of records from the result + """ diff --git a/src/gcore/types/cloud/load_balancers/listener_list_params.py b/src/gcore/types/cloud/load_balancers/listener_list_params.py index c3e9925f..d0eff492 100644 --- a/src/gcore/types/cloud/load_balancers/listener_list_params.py +++ b/src/gcore/types/cloud/load_balancers/listener_list_params.py @@ -14,8 +14,17 @@ class ListenerListParams(TypedDict, total=False): region_id: int """Region ID""" + limit: int + """Optional. Limit the number of returned items""" + load_balancer_id: str """Load Balancer ID""" + offset: int + """Optional. + + Offset value is used to exclude the first set of records from the result + """ + show_stats: bool """Show stats""" diff --git a/src/gcore/types/cloud/load_balancers/pool_list_params.py b/src/gcore/types/cloud/load_balancers/pool_list_params.py index 1cddb97b..fe83828f 100644 --- a/src/gcore/types/cloud/load_balancers/pool_list_params.py +++ b/src/gcore/types/cloud/load_balancers/pool_list_params.py @@ -17,8 +17,17 @@ class PoolListParams(TypedDict, total=False): details: bool """Show members and Health Monitor details""" + limit: int + """Optional. Limit the number of returned items""" + listener_id: str """Listener ID""" load_balancer_id: str """Load Balancer ID""" + + offset: int + """Optional. + + Offset value is used to exclude the first set of records from the result + """ diff --git a/src/gcore/types/cloud/security_group_create_params.py b/src/gcore/types/cloud/security_group_create_params.py index e660dee3..c56ab8e2 100644 --- a/src/gcore/types/cloud/security_group_create_params.py +++ b/src/gcore/types/cloud/security_group_create_params.py @@ -53,33 +53,34 @@ class Rule(TypedDict, total=False): port_range_min: Optional[int] """The minimum port number in the range that is matched by the security group rule""" - protocol: Literal[ - "ah", - "any", - "dccp", - "egp", - "esp", - "gre", - "icmp", - "igmp", - "ipencap", - "ipip", - "ipv6-encap", - "ipv6-frag", - "ipv6-icmp", - "ipv6-nonxt", - "ipv6-opts", - "ipv6-route", - "ospf", - "pgm", - "rsvp", - "sctp", - "tcp", - "udp", - "udplite", - "vrrp", + protocol: Optional[ + Literal[ + "ah", + "dccp", + "egp", + "esp", + "gre", + "icmp", + "igmp", + "ipencap", + "ipip", + "ipv6-encap", + "ipv6-frag", + "ipv6-icmp", + "ipv6-nonxt", + "ipv6-opts", + "ipv6-route", + "ospf", + "pgm", + "rsvp", + "sctp", + "tcp", + "udp", + "udplite", + "vrrp", + ] ] - """Protocol""" + """V2 protocol enum without 'any'. Use null for all protocols instead.""" remote_group_id: str """The remote group UUID to associate with this security group""" diff --git a/src/gcore/types/cloud/security_group_update_params.py b/src/gcore/types/cloud/security_group_update_params.py index a5e34df0..55f327aa 100644 --- a/src/gcore/types/cloud/security_group_update_params.py +++ b/src/gcore/types/cloud/security_group_update_params.py @@ -74,33 +74,34 @@ class Rule(TypedDict, total=False): port_range_min: int """The minimum port number in the range that is matched by the security group rule""" - protocol: Literal[ - "ah", - "any", - "dccp", - "egp", - "esp", - "gre", - "icmp", - "igmp", - "ipencap", - "ipip", - "ipv6-encap", - "ipv6-frag", - "ipv6-icmp", - "ipv6-nonxt", - "ipv6-opts", - "ipv6-route", - "ospf", - "pgm", - "rsvp", - "sctp", - "tcp", - "udp", - "udplite", - "vrrp", + protocol: Optional[ + Literal[ + "ah", + "dccp", + "egp", + "esp", + "gre", + "icmp", + "igmp", + "ipencap", + "ipip", + "ipv6-encap", + "ipv6-frag", + "ipv6-icmp", + "ipv6-nonxt", + "ipv6-opts", + "ipv6-route", + "ospf", + "pgm", + "rsvp", + "sctp", + "tcp", + "udp", + "udplite", + "vrrp", + ] ] - """Protocol""" + """V2 protocol enum without 'any'. Use null for all protocols instead.""" remote_group_id: str """The remote group UUID to associate with this security group rule""" diff --git a/src/gcore/types/cloud/security_groups/rule_create_params.py b/src/gcore/types/cloud/security_groups/rule_create_params.py index 46fdd411..29f1216e 100644 --- a/src/gcore/types/cloud/security_groups/rule_create_params.py +++ b/src/gcore/types/cloud/security_groups/rule_create_params.py @@ -32,33 +32,34 @@ class RuleCreateParams(TypedDict, total=False): port_range_min: Optional[int] """The minimum port number in the range that is matched by the security group rule""" - protocol: Literal[ - "ah", - "any", - "dccp", - "egp", - "esp", - "gre", - "icmp", - "igmp", - "ipencap", - "ipip", - "ipv6-encap", - "ipv6-frag", - "ipv6-icmp", - "ipv6-nonxt", - "ipv6-opts", - "ipv6-route", - "ospf", - "pgm", - "rsvp", - "sctp", - "tcp", - "udp", - "udplite", - "vrrp", + protocol: Optional[ + Literal[ + "ah", + "dccp", + "egp", + "esp", + "gre", + "icmp", + "igmp", + "ipencap", + "ipip", + "ipv6-encap", + "ipv6-frag", + "ipv6-icmp", + "ipv6-nonxt", + "ipv6-opts", + "ipv6-route", + "ospf", + "pgm", + "rsvp", + "sctp", + "tcp", + "udp", + "udplite", + "vrrp", + ] ] - """Protocol""" + """V2 protocol enum without 'any'. Use null for all protocols instead.""" remote_group_id: str """The remote group UUID to associate with this security group""" diff --git a/tests/api_resources/cloud/load_balancers/l7_policies/test_rules.py b/tests/api_resources/cloud/load_balancers/l7_policies/test_rules.py index 93b6eaf5..c4649689 100644 --- a/tests/api_resources/cloud/load_balancers/l7_policies/test_rules.py +++ b/tests/api_resources/cloud/load_balancers/l7_policies/test_rules.py @@ -99,6 +99,17 @@ def test_method_list(self, client: Gcore) -> None: ) assert_matches_type(LoadBalancerL7RuleList, rule, path=["response"]) + @parametrize + def test_method_list_with_all_params(self, client: Gcore) -> None: + rule = client.cloud.load_balancers.l7_policies.rules.list( + l7policy_id="023f2e34-7806-443b-bfae-16c324569a3d", + project_id=1, + region_id=1, + limit=1000, + offset=0, + ) + assert_matches_type(LoadBalancerL7RuleList, rule, path=["response"]) + @parametrize def test_raw_response_list(self, client: Gcore) -> None: response = client.cloud.load_balancers.l7_policies.rules.with_raw_response.list( @@ -414,6 +425,17 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: ) assert_matches_type(LoadBalancerL7RuleList, rule, path=["response"]) + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: + rule = await async_client.cloud.load_balancers.l7_policies.rules.list( + l7policy_id="023f2e34-7806-443b-bfae-16c324569a3d", + project_id=1, + region_id=1, + limit=1000, + offset=0, + ) + assert_matches_type(LoadBalancerL7RuleList, rule, path=["response"]) + @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.load_balancers.l7_policies.rules.with_raw_response.list( diff --git a/tests/api_resources/cloud/load_balancers/test_flavors.py b/tests/api_resources/cloud/load_balancers/test_flavors.py index 49f14df1..43ddefdd 100644 --- a/tests/api_resources/cloud/load_balancers/test_flavors.py +++ b/tests/api_resources/cloud/load_balancers/test_flavors.py @@ -31,6 +31,8 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=7, include_prices=True, + limit=1000, + offset=0, ) assert_matches_type(LoadBalancerFlavorList, flavor, path=["response"]) @@ -80,6 +82,8 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No project_id=1, region_id=7, include_prices=True, + limit=1000, + offset=0, ) assert_matches_type(LoadBalancerFlavorList, flavor, path=["response"]) diff --git a/tests/api_resources/cloud/load_balancers/test_l7_policies.py b/tests/api_resources/cloud/load_balancers/test_l7_policies.py index aae51218..88520e01 100644 --- a/tests/api_resources/cloud/load_balancers/test_l7_policies.py +++ b/tests/api_resources/cloud/load_balancers/test_l7_policies.py @@ -520,6 +520,16 @@ def test_method_list(self, client: Gcore) -> None: ) assert_matches_type(LoadBalancerL7PolicyList, l7_policy, path=["response"]) + @parametrize + def test_method_list_with_all_params(self, client: Gcore) -> None: + l7_policy = client.cloud.load_balancers.l7_policies.list( + project_id=1, + region_id=1, + limit=1000, + offset=0, + ) + assert_matches_type(LoadBalancerL7PolicyList, l7_policy, path=["response"]) + @parametrize def test_raw_response_list(self, client: Gcore) -> None: response = client.cloud.load_balancers.l7_policies.with_raw_response.list( @@ -1147,6 +1157,16 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: ) assert_matches_type(LoadBalancerL7PolicyList, l7_policy, path=["response"]) + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: + l7_policy = await async_client.cloud.load_balancers.l7_policies.list( + project_id=1, + region_id=1, + limit=1000, + offset=0, + ) + assert_matches_type(LoadBalancerL7PolicyList, l7_policy, path=["response"]) + @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.load_balancers.l7_policies.with_raw_response.list( diff --git a/tests/api_resources/cloud/load_balancers/test_listeners.py b/tests/api_resources/cloud/load_balancers/test_listeners.py index 81c7a2b2..4f19fe59 100644 --- a/tests/api_resources/cloud/load_balancers/test_listeners.py +++ b/tests/api_resources/cloud/load_balancers/test_listeners.py @@ -173,7 +173,9 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: listener = client.cloud.load_balancers.listeners.list( project_id=1, region_id=1, + limit=1000, load_balancer_id="00000000-0000-4000-8000-000000000000", + offset=0, show_stats=True, ) assert_matches_type(LoadBalancerListenerList, listener, path=["response"]) @@ -478,7 +480,9 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No listener = await async_client.cloud.load_balancers.listeners.list( project_id=1, region_id=1, + limit=1000, load_balancer_id="00000000-0000-4000-8000-000000000000", + offset=0, show_stats=True, ) assert_matches_type(LoadBalancerListenerList, listener, path=["response"]) diff --git a/tests/api_resources/cloud/load_balancers/test_pools.py b/tests/api_resources/cloud/load_balancers/test_pools.py index 9905e435..0a97278a 100644 --- a/tests/api_resources/cloud/load_balancers/test_pools.py +++ b/tests/api_resources/cloud/load_balancers/test_pools.py @@ -233,8 +233,10 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=1, details=True, + limit=1000, listener_id="00000000-0000-4000-8000-000000000000", load_balancer_id="00000000-0000-4000-8000-000000000000", + offset=0, ) assert_matches_type(LoadBalancerPoolList, pool, path=["response"]) @@ -578,8 +580,10 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No project_id=1, region_id=1, details=True, + limit=1000, listener_id="00000000-0000-4000-8000-000000000000", load_balancer_id="00000000-0000-4000-8000-000000000000", + offset=0, ) assert_matches_type(LoadBalancerPoolList, pool, path=["response"]) diff --git a/tests/api_resources/cloud/test_load_balancers.py b/tests/api_resources/cloud/test_load_balancers.py index 2c5764ef..d7e2014c 100644 --- a/tests/api_resources/cloud/test_load_balancers.py +++ b/tests/api_resources/cloud/test_load_balancers.py @@ -28,6 +28,7 @@ def test_method_create(self, client: Gcore) -> None: load_balancer = client.cloud.load_balancers.create( project_id=1, region_id=7, + name="new_load_balancer", ) assert_matches_type(TaskIDList, load_balancer, path=["response"]) @@ -36,6 +37,7 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: load_balancer = client.cloud.load_balancers.create( project_id=1, region_id=7, + name="new_load_balancer", flavor="lb1-1-2", floating_ip={ "existing_floating_id": "c64e5db1-5f1f-43ec-a8d9-5090df85b82d", @@ -124,8 +126,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "retention_policy": {"period": 45}, "topic_name": "my-log-name", }, - name="new_load_balancer", - name_template="lb_name_template", preferred_connectivity="L2", tags={"my-tag": "my-tag-value"}, vip_ip_family="dual", @@ -140,6 +140,7 @@ def test_raw_response_create(self, client: Gcore) -> None: response = client.cloud.load_balancers.with_raw_response.create( project_id=1, region_id=7, + name="new_load_balancer", ) assert response.is_closed is True @@ -152,6 +153,7 @@ def test_streaming_response_create(self, client: Gcore) -> None: with client.cloud.load_balancers.with_streaming_response.create( project_id=1, region_id=7, + name="new_load_balancer", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -504,6 +506,7 @@ async def test_method_create(self, async_client: AsyncGcore) -> None: load_balancer = await async_client.cloud.load_balancers.create( project_id=1, region_id=7, + name="new_load_balancer", ) assert_matches_type(TaskIDList, load_balancer, path=["response"]) @@ -512,6 +515,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> load_balancer = await async_client.cloud.load_balancers.create( project_id=1, region_id=7, + name="new_load_balancer", flavor="lb1-1-2", floating_ip={ "existing_floating_id": "c64e5db1-5f1f-43ec-a8d9-5090df85b82d", @@ -600,8 +604,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "retention_policy": {"period": 45}, "topic_name": "my-log-name", }, - name="new_load_balancer", - name_template="lb_name_template", preferred_connectivity="L2", tags={"my-tag": "my-tag-value"}, vip_ip_family="dual", @@ -616,6 +618,7 @@ async def test_raw_response_create(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.load_balancers.with_raw_response.create( project_id=1, region_id=7, + name="new_load_balancer", ) assert response.is_closed is True @@ -628,6 +631,7 @@ async def test_streaming_response_create(self, async_client: AsyncGcore) -> None async with async_client.cloud.load_balancers.with_streaming_response.create( project_id=1, region_id=7, + name="new_load_balancer", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From eda0539c7bac06e3451703469aabf50fb5290467 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:43:17 +0000 Subject: [PATCH 09/19] feat(api): aggregated API specs update --- .stats.yml | 4 +- src/gcore/resources/cdn/origin_groups.py | 192 ++++++++++++------ .../types/cdn/origin_group_create_params.py | 23 ++- .../types/cdn/origin_group_replace_params.py | 61 ++++-- .../types/cdn/origin_group_update_params.py | 37 +++- src/gcore/types/cdn/origin_groups.py | 41 +++- tests/api_resources/cdn/test_origin_groups.py | 30 +-- 7 files changed, 266 insertions(+), 122 deletions(-) diff --git a/.stats.yml b/.stats.yml index 705316e9..febda490 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-d22228888cb7ed9c880f41e9642e4e3f406c3401d5ef7ba8cb3674f12864682c.yml -openapi_spec_hash: a150cff71663ca3fc455be832781e306 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-13b4a16cd43e7638a0158083372e57034eff7a5024c83e70a7673823bad77b34.yml +openapi_spec_hash: 6b0a3adf49edd6629275fc3a033c391b config_hash: a8091c31251bc8979abd6db1f2c88983 diff --git a/src/gcore/resources/cdn/origin_groups.py b/src/gcore/resources/cdn/origin_groups.py index 01a4a919..d3ba0dbb 100644 --- a/src/gcore/resources/cdn/origin_groups.py +++ b/src/gcore/resources/cdn/origin_groups.py @@ -76,7 +76,9 @@ def create( Args: name: Origin group name. - auth_type: Origin authentication type. + auth_type: **Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: @@ -139,9 +141,15 @@ def create( Create an origin group with one or more origin sources. Args: - auth: Credentials to access the private bucket. + auth: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + + auth_type: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. - auth_type: Authentication type. + Authentication type. **awsSignatureV4** value is used for S3 storage. @@ -246,14 +254,19 @@ def update( Args: name: Origin group name. - auth_type: Origin authentication type. + auth_type: **Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: - **none** - Used for public origins. - **awsSignatureV4** - Used for S3 storage. - path: Parameter is **deprecated**. + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. proxy_next_upstream: Defines cases when the request should be passed on to the next origin. @@ -313,15 +326,24 @@ def update( Change origin group Args: - auth: Credentials to access the private bucket. + auth: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. - auth_type: Authentication type. + Credentials to access the private bucket. + + auth_type: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Authentication type. **awsSignatureV4** value is used for S3 storage. name: Origin group name. - path: Parameter is **deprecated**. + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. proxy_next_upstream: Defines cases when the request should be passed on to the next origin. @@ -537,11 +559,11 @@ def replace( self, origin_group_id: int, *, - auth_type: str, name: str, - path: str, sources: Iterable[origin_group_replace_params.NoneAuthSource], use_next: bool, + auth_type: str | Omit = omit, + path: str | Omit = omit, proxy_next_upstream: SequenceNotStr[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. @@ -554,17 +576,8 @@ def replace( Change origin group Args: - auth_type: Origin authentication type. - - Possible values: - - - **none** - Used for public origins. - - **awsSignatureV4** - Used for S3 storage. - name: Origin group name. - path: Parameter is **deprecated**. - use_next: Defines whether to use the next origin from the origin group if origin responds with the cases specified in `proxy_next_upstream`. If you enable it, you must specify cases in `proxy_next_upstream`. @@ -574,6 +587,20 @@ def replace( - **true** - Option is enabled. - **false** - Option is disabled. + auth_type: **Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. + + Possible values: + + - **none** - Used for public origins. + - **awsSignatureV4** - Used for S3 storage. + + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. + proxy_next_upstream: Defines cases when the request should be passed on to the next origin. Possible values: @@ -609,8 +636,8 @@ def replace( auth: origin_group_replace_params.AwsSignatureV4Auth, auth_type: str, name: str, - path: str, use_next: bool, + path: str | Omit = omit, proxy_next_upstream: SequenceNotStr[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. @@ -623,16 +650,20 @@ def replace( Change origin group Args: - auth: Credentials to access the private bucket. + auth: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + + auth_type: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. - auth_type: Authentication type. + Authentication type. **awsSignatureV4** value is used for S3 storage. name: Origin group name. - path: Parameter is **deprecated**. - use_next: Defines whether to use the next origin from the origin group if origin responds with the cases specified in `proxy_next_upstream`. If you enable it, you must specify cases in `proxy_next_upstream`. @@ -642,6 +673,11 @@ def replace( - **true** - Option is enabled. - **false** - Option is disabled. + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. + proxy_next_upstream: Defines cases when the request should be passed on to the next origin. Possible values: @@ -669,18 +705,16 @@ def replace( """ ... - @required_args( - ["auth_type", "name", "path", "sources", "use_next"], ["auth", "auth_type", "name", "path", "use_next"] - ) + @required_args(["name", "sources", "use_next"], ["auth", "auth_type", "name", "use_next"]) def replace( self, origin_group_id: int, *, - auth_type: str, name: str, - path: str, sources: Iterable[origin_group_replace_params.NoneAuthSource] | Omit = omit, use_next: bool, + auth_type: str | Omit = omit, + path: str | Omit = omit, proxy_next_upstream: SequenceNotStr[str] | Omit = omit, auth: origin_group_replace_params.AwsSignatureV4Auth | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -696,11 +730,11 @@ def replace( path_template("/cdn/origin_groups/{origin_group_id}", origin_group_id=origin_group_id), body=maybe_transform( { - "auth_type": auth_type, "name": name, - "path": path, "sources": sources, "use_next": use_next, + "auth_type": auth_type, + "path": path, "proxy_next_upstream": proxy_next_upstream, "auth": auth, }, @@ -760,7 +794,9 @@ async def create( Args: name: Origin group name. - auth_type: Origin authentication type. + auth_type: **Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: @@ -823,9 +859,15 @@ async def create( Create an origin group with one or more origin sources. Args: - auth: Credentials to access the private bucket. + auth: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + + auth_type: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. - auth_type: Authentication type. + Authentication type. **awsSignatureV4** value is used for S3 storage. @@ -930,14 +972,19 @@ async def update( Args: name: Origin group name. - auth_type: Origin authentication type. + auth_type: **Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: - **none** - Used for public origins. - **awsSignatureV4** - Used for S3 storage. - path: Parameter is **deprecated**. + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. proxy_next_upstream: Defines cases when the request should be passed on to the next origin. @@ -997,15 +1044,24 @@ async def update( Change origin group Args: - auth: Credentials to access the private bucket. + auth: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. - auth_type: Authentication type. + Credentials to access the private bucket. + + auth_type: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Authentication type. **awsSignatureV4** value is used for S3 storage. name: Origin group name. - path: Parameter is **deprecated**. + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. proxy_next_upstream: Defines cases when the request should be passed on to the next origin. @@ -1221,11 +1277,11 @@ async def replace( self, origin_group_id: int, *, - auth_type: str, name: str, - path: str, sources: Iterable[origin_group_replace_params.NoneAuthSource], use_next: bool, + auth_type: str | Omit = omit, + path: str | Omit = omit, proxy_next_upstream: SequenceNotStr[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. @@ -1238,17 +1294,8 @@ async def replace( Change origin group Args: - auth_type: Origin authentication type. - - Possible values: - - - **none** - Used for public origins. - - **awsSignatureV4** - Used for S3 storage. - name: Origin group name. - path: Parameter is **deprecated**. - use_next: Defines whether to use the next origin from the origin group if origin responds with the cases specified in `proxy_next_upstream`. If you enable it, you must specify cases in `proxy_next_upstream`. @@ -1258,6 +1305,20 @@ async def replace( - **true** - Option is enabled. - **false** - Option is disabled. + auth_type: **Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. + + Possible values: + + - **none** - Used for public origins. + - **awsSignatureV4** - Used for S3 storage. + + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. + proxy_next_upstream: Defines cases when the request should be passed on to the next origin. Possible values: @@ -1293,8 +1354,8 @@ async def replace( auth: origin_group_replace_params.AwsSignatureV4Auth, auth_type: str, name: str, - path: str, use_next: bool, + path: str | Omit = omit, proxy_next_upstream: SequenceNotStr[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. @@ -1307,16 +1368,20 @@ async def replace( Change origin group Args: - auth: Credentials to access the private bucket. + auth: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + + auth_type: **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. - auth_type: Authentication type. + Authentication type. **awsSignatureV4** value is used for S3 storage. name: Origin group name. - path: Parameter is **deprecated**. - use_next: Defines whether to use the next origin from the origin group if origin responds with the cases specified in `proxy_next_upstream`. If you enable it, you must specify cases in `proxy_next_upstream`. @@ -1326,6 +1391,11 @@ async def replace( - **true** - Option is enabled. - **false** - Option is disabled. + path: **Deprecated.** No longer necessary. Omit this field and the default origin path + behavior will be used. + + Origin path prefix. + proxy_next_upstream: Defines cases when the request should be passed on to the next origin. Possible values: @@ -1353,18 +1423,16 @@ async def replace( """ ... - @required_args( - ["auth_type", "name", "path", "sources", "use_next"], ["auth", "auth_type", "name", "path", "use_next"] - ) + @required_args(["name", "sources", "use_next"], ["auth", "auth_type", "name", "use_next"]) async def replace( self, origin_group_id: int, *, - auth_type: str, name: str, - path: str, sources: Iterable[origin_group_replace_params.NoneAuthSource] | Omit = omit, use_next: bool, + auth_type: str | Omit = omit, + path: str | Omit = omit, proxy_next_upstream: SequenceNotStr[str] | Omit = omit, auth: origin_group_replace_params.AwsSignatureV4Auth | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -1380,11 +1448,11 @@ async def replace( path_template("/cdn/origin_groups/{origin_group_id}", origin_group_id=origin_group_id), body=await async_maybe_transform( { - "auth_type": auth_type, "name": name, - "path": path, "sources": sources, "use_next": use_next, + "auth_type": auth_type, + "path": path, "proxy_next_upstream": proxy_next_upstream, "auth": auth, }, diff --git a/src/gcore/types/cdn/origin_group_create_params.py b/src/gcore/types/cdn/origin_group_create_params.py index 10e256e5..8efbd7d2 100644 --- a/src/gcore/types/cdn/origin_group_create_params.py +++ b/src/gcore/types/cdn/origin_group_create_params.py @@ -26,7 +26,9 @@ class NoneAuth(TypedDict, total=False): sources: Required[Iterable[NoneAuthSource]] auth_type: str - """Origin authentication type. + """**Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: @@ -204,10 +206,19 @@ class NoneAuthSourceChangeS3Source(TypedDict, total=False): class AwsSignatureV4(TypedDict, total=False): auth: Required[AwsSignatureV4Auth] - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ auth_type: Required[str] - """Authentication type. + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Authentication type. **awsSignatureV4** value is used for S3 storage. """ @@ -248,7 +259,11 @@ class AwsSignatureV4(TypedDict, total=False): class AwsSignatureV4Auth(TypedDict, total=False): - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ s3_access_key_id: Required[str] """Access key ID for the S3 account. diff --git a/src/gcore/types/cdn/origin_group_replace_params.py b/src/gcore/types/cdn/origin_group_replace_params.py index 4a93b73b..a8694a66 100644 --- a/src/gcore/types/cdn/origin_group_replace_params.py +++ b/src/gcore/types/cdn/origin_group_replace_params.py @@ -20,21 +20,9 @@ class NoneAuth(TypedDict, total=False): - auth_type: Required[str] - """Origin authentication type. - - Possible values: - - - **none** - Used for public origins. - - **awsSignatureV4** - Used for S3 storage. - """ - name: Required[str] """Origin group name.""" - path: Required[str] - """Parameter is **deprecated**.""" - sources: Required[Iterable[NoneAuthSource]] use_next: Required[bool] @@ -49,6 +37,25 @@ class NoneAuth(TypedDict, total=False): - **false** - Option is disabled. """ + auth_type: str + """**Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. + + Possible values: + + - **none** - Used for public origins. + - **awsSignatureV4** - Used for S3 storage. + """ + + path: str + """**Deprecated.** No longer necessary. + + Omit this field and the default origin path behavior will be used. + + Origin path prefix. + """ + proxy_next_upstream: SequenceNotStr[str] """Defines cases when the request should be passed on to the next origin. @@ -207,10 +214,19 @@ class NoneAuthSourceChangeS3Source(TypedDict, total=False): class AwsSignatureV4(TypedDict, total=False): auth: Required[AwsSignatureV4Auth] - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ auth_type: Required[str] - """Authentication type. + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Authentication type. **awsSignatureV4** value is used for S3 storage. """ @@ -218,9 +234,6 @@ class AwsSignatureV4(TypedDict, total=False): name: Required[str] """Origin group name.""" - path: Required[str] - """Parameter is **deprecated**.""" - use_next: Required[bool] """ Defines whether to use the next origin from the origin group if origin responds @@ -233,6 +246,14 @@ class AwsSignatureV4(TypedDict, total=False): - **false** - Option is disabled. """ + path: str + """**Deprecated.** No longer necessary. + + Omit this field and the default origin path behavior will be used. + + Origin path prefix. + """ + proxy_next_upstream: SequenceNotStr[str] """Defines cases when the request should be passed on to the next origin. @@ -254,7 +275,11 @@ class AwsSignatureV4(TypedDict, total=False): class AwsSignatureV4Auth(TypedDict, total=False): - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ s3_access_key_id: Required[str] """Access key ID for the S3 account. diff --git a/src/gcore/types/cdn/origin_group_update_params.py b/src/gcore/types/cdn/origin_group_update_params.py index 39593b45..63a0cb0f 100644 --- a/src/gcore/types/cdn/origin_group_update_params.py +++ b/src/gcore/types/cdn/origin_group_update_params.py @@ -24,7 +24,9 @@ class NoneAuth(TypedDict, total=False): """Origin group name.""" auth_type: str - """Origin authentication type. + """**Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: @@ -33,7 +35,12 @@ class NoneAuth(TypedDict, total=False): """ path: str - """Parameter is **deprecated**.""" + """**Deprecated.** No longer necessary. + + Omit this field and the default origin path behavior will be used. + + Origin path prefix. + """ proxy_next_upstream: SequenceNotStr[str] """Defines cases when the request should be passed on to the next origin. @@ -207,10 +214,19 @@ class NoneAuthSourceChangeS3Source(TypedDict, total=False): class AwsSignatureV4(TypedDict, total=False): auth: AwsSignatureV4Auth - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ auth_type: str - """Authentication type. + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Authentication type. **awsSignatureV4** value is used for S3 storage. """ @@ -219,7 +235,12 @@ class AwsSignatureV4(TypedDict, total=False): """Origin group name.""" path: str - """Parameter is **deprecated**.""" + """**Deprecated.** No longer necessary. + + Omit this field and the default origin path behavior will be used. + + Origin path prefix. + """ proxy_next_upstream: SequenceNotStr[str] """Defines cases when the request should be passed on to the next origin. @@ -254,7 +275,11 @@ class AwsSignatureV4(TypedDict, total=False): class AwsSignatureV4Auth(TypedDict, total=False): - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ s3_access_key_id: Required[str] """Access key ID for the S3 account. diff --git a/src/gcore/types/cdn/origin_groups.py b/src/gcore/types/cdn/origin_groups.py index 4bf02eb8..43a63d92 100644 --- a/src/gcore/types/cdn/origin_groups.py +++ b/src/gcore/types/cdn/origin_groups.py @@ -179,7 +179,9 @@ class NoneAuth(BaseModel): """ auth_type: Optional[str] = None - """Origin authentication type. + """**Deprecated.** No longer necessary. Defaults to `none`. + + Origin authentication type. Possible values: @@ -197,7 +199,12 @@ class NoneAuth(BaseModel): """ path: Optional[str] = None - """Parameter is **deprecated**.""" + """**Deprecated.** No longer necessary. + + Omit this field and the default origin path behavior will be used. + + Origin path prefix. + """ proxy_next_upstream: Optional[List[str]] = None """Defines cases when the request should be passed on to the next origin. @@ -232,7 +239,11 @@ class NoneAuth(BaseModel): class AwsSignatureV4Auth(BaseModel): - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ s3_access_key_id: str """Access key ID for the S3 account. @@ -280,14 +291,27 @@ class AwsSignatureV4Auth(BaseModel): class AwsSignatureV4(BaseModel): + """ + **Deprecated.** To create S3 origins, configure them directly in sources with `origin_type` and `config` instead. + """ + id: int """Origin group ID.""" auth: AwsSignatureV4Auth - """Credentials to access the private bucket.""" + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Credentials to access the private bucket. + """ auth_type: str - """Authentication type. + """ + **Deprecated.** To create S3 origins, configure them directly in sources with + `origin_type` and `config` instead. + + Authentication type. **awsSignatureV4** value is used for S3 storage. """ @@ -305,7 +329,12 @@ class AwsSignatureV4(BaseModel): """ path: Optional[str] = None - """Parameter is **deprecated**.""" + """**Deprecated.** No longer necessary. + + Omit this field and the default origin path behavior will be used. + + Origin path prefix. + """ proxy_next_upstream: Optional[List[str]] = None """Defines cases when the request should be passed on to the next origin. diff --git a/tests/api_resources/cdn/test_origin_groups.py b/tests/api_resources/cdn/test_origin_groups.py index 58a1b94f..cc9e84b7 100644 --- a/tests/api_resources/cdn/test_origin_groups.py +++ b/tests/api_resources/cdn/test_origin_groups.py @@ -351,9 +351,7 @@ def test_streaming_response_get(self, client: Gcore) -> None: def test_method_replace_overload_1(self, client: Gcore) -> None: origin_group = client.cdn.origin_groups.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[{"source": "yourdomain.com"}], use_next=True, ) @@ -363,9 +361,7 @@ def test_method_replace_overload_1(self, client: Gcore) -> None: def test_method_replace_with_all_params_overload_1(self, client: Gcore) -> None: origin_group = client.cdn.origin_groups.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[ { "source": "yourdomain.com", @@ -376,6 +372,8 @@ def test_method_replace_with_all_params_overload_1(self, client: Gcore) -> None: } ], use_next=True, + auth_type="none", + path="", proxy_next_upstream=["error", "timeout", "invalid_header", "http_500", "http_502", "http_503", "http_504"], ) assert_matches_type(OriginGroups, origin_group, path=["response"]) @@ -384,9 +382,7 @@ def test_method_replace_with_all_params_overload_1(self, client: Gcore) -> None: def test_raw_response_replace_overload_1(self, client: Gcore) -> None: response = client.cdn.origin_groups.with_raw_response.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[{"source": "yourdomain.com"}], use_next=True, ) @@ -400,9 +396,7 @@ def test_raw_response_replace_overload_1(self, client: Gcore) -> None: def test_streaming_response_replace_overload_1(self, client: Gcore) -> None: with client.cdn.origin_groups.with_streaming_response.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[{"source": "yourdomain.com"}], use_next=True, ) as response: @@ -426,7 +420,6 @@ def test_method_replace_overload_2(self, client: Gcore) -> None: }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, ) assert_matches_type(OriginGroups, origin_group, path=["response"]) @@ -445,8 +438,8 @@ def test_method_replace_with_all_params_overload_2(self, client: Gcore) -> None: }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, + path="", proxy_next_upstream=["error", "timeout", "invalid_header", "http_500", "http_502", "http_503", "http_504"], ) assert_matches_type(OriginGroups, origin_group, path=["response"]) @@ -463,7 +456,6 @@ def test_raw_response_replace_overload_2(self, client: Gcore) -> None: }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, ) @@ -484,7 +476,6 @@ def test_streaming_response_replace_overload_2(self, client: Gcore) -> None: }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, ) as response: assert not response.is_closed @@ -832,9 +823,7 @@ async def test_streaming_response_get(self, async_client: AsyncGcore) -> None: async def test_method_replace_overload_1(self, async_client: AsyncGcore) -> None: origin_group = await async_client.cdn.origin_groups.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[{"source": "yourdomain.com"}], use_next=True, ) @@ -844,9 +833,7 @@ async def test_method_replace_overload_1(self, async_client: AsyncGcore) -> None async def test_method_replace_with_all_params_overload_1(self, async_client: AsyncGcore) -> None: origin_group = await async_client.cdn.origin_groups.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[ { "source": "yourdomain.com", @@ -857,6 +844,8 @@ async def test_method_replace_with_all_params_overload_1(self, async_client: Asy } ], use_next=True, + auth_type="none", + path="", proxy_next_upstream=["error", "timeout", "invalid_header", "http_500", "http_502", "http_503", "http_504"], ) assert_matches_type(OriginGroups, origin_group, path=["response"]) @@ -865,9 +854,7 @@ async def test_method_replace_with_all_params_overload_1(self, async_client: Asy async def test_raw_response_replace_overload_1(self, async_client: AsyncGcore) -> None: response = await async_client.cdn.origin_groups.with_raw_response.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[{"source": "yourdomain.com"}], use_next=True, ) @@ -881,9 +868,7 @@ async def test_raw_response_replace_overload_1(self, async_client: AsyncGcore) - async def test_streaming_response_replace_overload_1(self, async_client: AsyncGcore) -> None: async with async_client.cdn.origin_groups.with_streaming_response.replace( origin_group_id=0, - auth_type="none", name="YourOriginGroup", - path="", sources=[{"source": "yourdomain.com"}], use_next=True, ) as response: @@ -907,7 +892,6 @@ async def test_method_replace_overload_2(self, async_client: AsyncGcore) -> None }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, ) assert_matches_type(OriginGroups, origin_group, path=["response"]) @@ -926,8 +910,8 @@ async def test_method_replace_with_all_params_overload_2(self, async_client: Asy }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, + path="", proxy_next_upstream=["error", "timeout", "invalid_header", "http_500", "http_502", "http_503", "http_504"], ) assert_matches_type(OriginGroups, origin_group, path=["response"]) @@ -944,7 +928,6 @@ async def test_raw_response_replace_overload_2(self, async_client: AsyncGcore) - }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, ) @@ -965,7 +948,6 @@ async def test_streaming_response_replace_overload_2(self, async_client: AsyncGc }, auth_type="awsSignatureV4", name="YourOriginGroup", - path="", use_next=True, ) as response: assert not response.is_closed From 4b999011b48a859a6f95649246ac3a5ba67ff62e Mon Sep 17 00:00:00 2001 From: Pedro Oliveira <8281907+pedrodeoliveira@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:46:54 +0100 Subject: [PATCH 10/19] fix(cloud): align polling methods with underlying base method - Make `name` a required parameter for load balancer creation instead of optional, and remove the unused `name_template` parameter. - Remove `"any"` from the allowed `protocol` values in security group rule creation. --- .../resources/cloud/load_balancers/load_balancers.py | 8 ++------ src/gcore/resources/cloud/security_groups/rules.py | 2 -- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/gcore/resources/cloud/load_balancers/load_balancers.py b/src/gcore/resources/cloud/load_balancers/load_balancers.py index c9957907..7ca62e45 100644 --- a/src/gcore/resources/cloud/load_balancers/load_balancers.py +++ b/src/gcore/resources/cloud/load_balancers/load_balancers.py @@ -463,8 +463,7 @@ def create_and_poll( floating_ip: load_balancer_create_params.FloatingIP | Omit = omit, listeners: Iterable[load_balancer_create_params.Listener] | Omit = omit, logging: load_balancer_create_params.Logging | Omit = omit, - name: str | Omit = omit, - name_template: str | Omit = omit, + name: str, preferred_connectivity: LoadBalancerMemberConnectivity | Omit = omit, tags: Dict[str, str] | Omit = omit, vip_ip_family: InterfaceIPFamily | Omit = omit, @@ -488,7 +487,6 @@ def create_and_poll( listeners=listeners, logging=logging, name=name, - name_template=name_template, preferred_connectivity=preferred_connectivity, tags=tags, vip_ip_family=vip_ip_family, @@ -1249,8 +1247,7 @@ async def create_and_poll( floating_ip: load_balancer_create_params.FloatingIP | Omit = omit, listeners: Iterable[load_balancer_create_params.Listener] | Omit = omit, logging: load_balancer_create_params.Logging | Omit = omit, - name: str | Omit = omit, - name_template: str | Omit = omit, + name: str, preferred_connectivity: LoadBalancerMemberConnectivity | Omit = omit, tags: Dict[str, str] | Omit = omit, vip_ip_family: InterfaceIPFamily | Omit = omit, @@ -1274,7 +1271,6 @@ async def create_and_poll( listeners=listeners, logging=logging, name=name, - name_template=name_template, preferred_connectivity=preferred_connectivity, tags=tags, vip_ip_family=vip_ip_family, diff --git a/src/gcore/resources/cloud/security_groups/rules.py b/src/gcore/resources/cloud/security_groups/rules.py index 8260bd42..061c4274 100644 --- a/src/gcore/resources/cloud/security_groups/rules.py +++ b/src/gcore/resources/cloud/security_groups/rules.py @@ -362,7 +362,6 @@ def create_and_poll( port_range_min: Optional[int] | Omit = omit, protocol: Literal[ "ah", - "any", "dccp", "egp", "esp", @@ -824,7 +823,6 @@ async def create_and_poll( port_range_min: Optional[int] | Omit = omit, protocol: Literal[ "ah", - "any", "dccp", "egp", "esp", From 16f4262f6ca89edcefe0315c13963e2d84f945c2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 14:56:43 +0000 Subject: [PATCH 11/19] fix: ensure file data are only sent as 1 parameter --- src/gcore/_utils/_utils.py | 5 +++-- tests/test_extract_files.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/gcore/_utils/_utils.py b/src/gcore/_utils/_utils.py index eec7f4a1..63b8cd60 100644 --- a/src/gcore/_utils/_utils.py +++ b/src/gcore/_utils/_utils.py @@ -86,8 +86,9 @@ def _extract_items( index += 1 if is_dict(obj): try: - # We are at the last entry in the path so we must remove the field - if (len(path)) == index: + # Remove the field if there are no more dict keys in the path, + # only "" traversal markers or end. + if all(p == "" for p in path[index:]): item = obj.pop(key) else: item = obj[key] diff --git a/tests/test_extract_files.py b/tests/test_extract_files.py index d67473bc..e05e48bd 100644 --- a/tests/test_extract_files.py +++ b/tests/test_extract_files.py @@ -35,6 +35,15 @@ def test_multiple_files() -> None: assert query == {"documents": [{}, {}]} +def test_top_level_file_array() -> None: + query = {"files": [b"file one", b"file two"], "title": "hello"} + assert extract_files(query, paths=[["files", ""]]) == [ + ("files[]", b"file one"), + ("files[]", b"file two"), + ] + assert query == {"title": "hello"} + + @pytest.mark.parametrize( "query,paths,expected", [ From fa05d9370764f706015b7628abd3ba0f12f880d7 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:46:06 +0000 Subject: [PATCH 12/19] docs: update examples --- .../cloud/baremetal/test_servers.py | 10 ++++++++-- .../cloud/gpu_baremetal/test_clusters.py | 10 ++++++++-- .../cloud/gpu_virtual/test_clusters.py | 10 ++++++++-- .../cloud/instances/test_images.py | 4 ++-- .../cloud/instances/test_metrics.py | 16 +++++++-------- .../cloud/load_balancers/test_metrics.py | 16 +++++++-------- .../cloud/load_balancers/test_pools.py | 20 +++++++++---------- .../cloud/networks/test_subnets.py | 10 ++++++++-- tests/api_resources/cloud/test_file_shares.py | 10 ++++++++-- .../api_resources/cloud/test_floating_ips.py | 10 ++++++++-- tests/api_resources/cloud/test_instances.py | 10 ++++++++-- .../cloud/test_load_balancers.py | 14 +++++++++---- tests/api_resources/cloud/test_networks.py | 10 ++++++++-- .../cloud/test_reserved_fixed_ips.py | 8 ++++---- .../cloud/test_security_groups.py | 10 ++++++++-- .../cloud/test_volume_snapshots.py | 10 ++++++++-- tests/api_resources/cloud/test_volumes.py | 10 ++++++++-- 17 files changed, 130 insertions(+), 58 deletions(-) diff --git a/tests/api_resources/cloud/baremetal/test_servers.py b/tests/api_resources/cloud/baremetal/test_servers.py index 4897a5a6..2380807e 100644 --- a/tests/api_resources/cloud/baremetal/test_servers.py +++ b/tests/api_resources/cloud/baremetal/test_servers.py @@ -114,7 +114,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=1, name="server_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(BaremetalServer, server, path=["response"]) @@ -477,7 +480,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=1, region_id=1, name="server_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(BaremetalServer, server, path=["response"]) diff --git a/tests/api_resources/cloud/gpu_baremetal/test_clusters.py b/tests/api_resources/cloud/gpu_baremetal/test_clusters.py index e6b030a6..2771230f 100644 --- a/tests/api_resources/cloud/gpu_baremetal/test_clusters.py +++ b/tests/api_resources/cloud/gpu_baremetal/test_clusters.py @@ -124,7 +124,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=7, name="gpu-cluster-1", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) @@ -679,7 +682,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=1, region_id=7, name="gpu-cluster-1", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(GPUBaremetalCluster, cluster, path=["response"]) diff --git a/tests/api_resources/cloud/gpu_virtual/test_clusters.py b/tests/api_resources/cloud/gpu_virtual/test_clusters.py index 05bdb651..9c748c54 100644 --- a/tests/api_resources/cloud/gpu_virtual/test_clusters.py +++ b/tests/api_resources/cloud/gpu_virtual/test_clusters.py @@ -161,7 +161,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=7, name="gpu-cluster-1", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(GPUVirtualCluster, cluster, path=["response"]) @@ -753,7 +756,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=1, region_id=7, name="gpu-cluster-1", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(GPUVirtualCluster, cluster, path=["response"]) diff --git a/tests/api_resources/cloud/instances/test_images.py b/tests/api_resources/cloud/instances/test_images.py index 036159e2..ca1c5011 100644 --- a/tests/api_resources/cloud/instances/test_images.py +++ b/tests/api_resources/cloud/instances/test_images.py @@ -42,7 +42,7 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: name="my-image", os_type="linux", ssh_key="allow", - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, ) assert_matches_type(Image, image, path=["response"]) @@ -377,7 +377,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> name="my-image", os_type="linux", ssh_key="allow", - tags={"foo": "string"}, + tags={"my-tag": "my-tag-value"}, ) assert_matches_type(Image, image, path=["response"]) diff --git a/tests/api_resources/cloud/instances/test_metrics.py b/tests/api_resources/cloud/instances/test_metrics.py index afa61520..3d0ecc42 100644 --- a/tests/api_resources/cloud/instances/test_metrics.py +++ b/tests/api_resources/cloud/instances/test_metrics.py @@ -24,7 +24,7 @@ def test_method_list(self, client: Gcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) assert_matches_type(MetricsList, metric, path=["response"]) @@ -35,7 +35,7 @@ def test_raw_response_list(self, client: Gcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) assert response.is_closed is True @@ -50,7 +50,7 @@ def test_streaming_response_list(self, client: Gcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -68,7 +68,7 @@ def test_path_params_list(self, client: Gcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) @@ -84,7 +84,7 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) assert_matches_type(MetricsList, metric, path=["response"]) @@ -95,7 +95,7 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) assert response.is_closed is True @@ -110,7 +110,7 @@ async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -128,5 +128,5 @@ async def test_path_params_list(self, async_client: AsyncGcore) -> None: project_id=0, region_id=0, time_interval=6, - time_unit="day", + time_unit="hour", ) diff --git a/tests/api_resources/cloud/load_balancers/test_metrics.py b/tests/api_resources/cloud/load_balancers/test_metrics.py index 3ab21ffd..0a101467 100644 --- a/tests/api_resources/cloud/load_balancers/test_metrics.py +++ b/tests/api_resources/cloud/load_balancers/test_metrics.py @@ -24,7 +24,7 @@ def test_method_list(self, client: Gcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) assert_matches_type(LoadBalancerMetricsList, metric, path=["response"]) @@ -35,7 +35,7 @@ def test_raw_response_list(self, client: Gcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) assert response.is_closed is True @@ -50,7 +50,7 @@ def test_streaming_response_list(self, client: Gcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -68,7 +68,7 @@ def test_path_params_list(self, client: Gcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) @@ -84,7 +84,7 @@ async def test_method_list(self, async_client: AsyncGcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) assert_matches_type(LoadBalancerMetricsList, metric, path=["response"]) @@ -95,7 +95,7 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) assert response.is_closed is True @@ -110,7 +110,7 @@ async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -128,5 +128,5 @@ async def test_path_params_list(self, async_client: AsyncGcore) -> None: project_id=1, region_id=7, time_interval=6, - time_unit="day", + time_unit="hour", ) diff --git a/tests/api_resources/cloud/load_balancers/test_pools.py b/tests/api_resources/cloud/load_balancers/test_pools.py index 0a97278a..6a181999 100644 --- a/tests/api_resources/cloud/load_balancers/test_pools.py +++ b/tests/api_resources/cloud/load_balancers/test_pools.py @@ -22,7 +22,7 @@ def test_method_create(self, client: Gcore) -> None: pool = client.cloud.load_balancers.pools.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ) @@ -33,7 +33,7 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: pool = client.cloud.load_balancers.pools.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ca_secret_id="ca_secret_id", @@ -95,7 +95,7 @@ def test_raw_response_create(self, client: Gcore) -> None: response = client.cloud.load_balancers.pools.with_raw_response.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ) @@ -110,7 +110,7 @@ def test_streaming_response_create(self, client: Gcore) -> None: with client.cloud.load_balancers.pools.with_streaming_response.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ) as response: @@ -153,7 +153,7 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "type": "HTTP", "url_path": "/", }, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", members=[ { "address": "192.168.40.33", @@ -369,7 +369,7 @@ async def test_method_create(self, async_client: AsyncGcore) -> None: pool = await async_client.cloud.load_balancers.pools.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ) @@ -380,7 +380,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> pool = await async_client.cloud.load_balancers.pools.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ca_secret_id="ca_secret_id", @@ -442,7 +442,7 @@ async def test_raw_response_create(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.load_balancers.pools.with_raw_response.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ) @@ -457,7 +457,7 @@ async def test_streaming_response_create(self, async_client: AsyncGcore) -> None async with async_client.cloud.load_balancers.pools.with_streaming_response.create( project_id=1, region_id=1, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", name="pool_name", protocol="HTTP", ) as response: @@ -500,7 +500,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "type": "HTTP", "url_path": "/", }, - lb_algorithm="LEAST_CONNECTIONS", + lb_algorithm="ROUND_ROBIN", members=[ { "address": "192.168.40.33", diff --git a/tests/api_resources/cloud/networks/test_subnets.py b/tests/api_resources/cloud/networks/test_subnets.py index 58f950c6..6049bcc1 100644 --- a/tests/api_resources/cloud/networks/test_subnets.py +++ b/tests/api_resources/cloud/networks/test_subnets.py @@ -110,7 +110,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: } ], name="some_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Subnet, subnet, path=["response"]) @@ -390,7 +393,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> } ], name="some_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Subnet, subnet, path=["response"]) diff --git a/tests/api_resources/cloud/test_file_shares.py b/tests/api_resources/cloud/test_file_shares.py index 3d015d54..1348551b 100644 --- a/tests/api_resources/cloud/test_file_shares.py +++ b/tests/api_resources/cloud/test_file_shares.py @@ -174,7 +174,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "path_length": "LCD", "root_squash": True, }, - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(TaskIDList, file_share, path=["response"]) @@ -562,7 +565,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "path_length": "LCD", "root_squash": True, }, - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(TaskIDList, file_share, path=["response"]) diff --git a/tests/api_resources/cloud/test_floating_ips.py b/tests/api_resources/cloud/test_floating_ips.py index 55ac7904..4416197f 100644 --- a/tests/api_resources/cloud/test_floating_ips.py +++ b/tests/api_resources/cloud/test_floating_ips.py @@ -86,7 +86,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: region_id=1, fixed_ip_address="192.168.10.15", port_id="ee2402d0-f0cd-4503-9b75-69be1d11c5f1", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(TaskIDList, floating_ip, path=["response"]) @@ -453,7 +456,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> region_id=1, fixed_ip_address="192.168.10.15", port_id="ee2402d0-f0cd-4503-9b75-69be1d11c5f1", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(TaskIDList, floating_ip, path=["response"]) diff --git a/tests/api_resources/cloud/test_instances.py b/tests/api_resources/cloud/test_instances.py index dd5d7f45..49f04ef3 100644 --- a/tests/api_resources/cloud/test_instances.py +++ b/tests/api_resources/cloud/test_instances.py @@ -139,7 +139,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=0, region_id=0, name="instance_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Instance, instance, path=["response"]) @@ -1011,7 +1014,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=0, region_id=0, name="instance_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Instance, instance, path=["response"]) diff --git a/tests/api_resources/cloud/test_load_balancers.py b/tests/api_resources/cloud/test_load_balancers.py index d7e2014c..f760dc3e 100644 --- a/tests/api_resources/cloud/test_load_balancers.py +++ b/tests/api_resources/cloud/test_load_balancers.py @@ -128,7 +128,7 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: }, preferred_connectivity="L2", tags={"my-tag": "my-tag-value"}, - vip_ip_family="dual", + vip_ip_family="ipv4", vip_network_id="ac307687-31a4-4a11-a949-6bea1b2878f5", vip_port_id="ff83e13a-b256-4be2-ba5d-028d3f0ab450", vip_subnet_id="4e7802d3-5023-44b8-b298-7726558fddf4", @@ -189,7 +189,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: }, name="some_name", preferred_connectivity="L2", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(LoadBalancer, load_balancer, path=["response"]) @@ -606,7 +609,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> }, preferred_connectivity="L2", tags={"my-tag": "my-tag-value"}, - vip_ip_family="dual", + vip_ip_family="ipv4", vip_network_id="ac307687-31a4-4a11-a949-6bea1b2878f5", vip_port_id="ff83e13a-b256-4be2-ba5d-028d3f0ab450", vip_subnet_id="4e7802d3-5023-44b8-b298-7726558fddf4", @@ -667,7 +670,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> }, name="some_name", preferred_connectivity="L2", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(LoadBalancer, load_balancer, path=["response"]) diff --git a/tests/api_resources/cloud/test_networks.py b/tests/api_resources/cloud/test_networks.py index 7a46a77b..57d1d6ea 100644 --- a/tests/api_resources/cloud/test_networks.py +++ b/tests/api_resources/cloud/test_networks.py @@ -86,7 +86,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=1, name="some_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Network, network, path=["response"]) @@ -341,7 +344,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=1, region_id=1, name="some_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Network, network, path=["response"]) diff --git a/tests/api_resources/cloud/test_reserved_fixed_ips.py b/tests/api_resources/cloud/test_reserved_fixed_ips.py index 21a89074..536cbd65 100644 --- a/tests/api_resources/cloud/test_reserved_fixed_ips.py +++ b/tests/api_resources/cloud/test_reserved_fixed_ips.py @@ -36,7 +36,7 @@ def test_method_create_with_all_params_overload_1(self, client: Gcore) -> None: project_id=0, region_id=0, type="external", - ip_family="dual", + ip_family="ipv4", is_vip=False, ) assert_matches_type(TaskIDList, reserved_fixed_ip, path=["response"]) @@ -137,7 +137,7 @@ def test_method_create_with_all_params_overload_3(self, client: Gcore) -> None: region_id=0, network_id="e3c6ee77-48cb-416b-b204-11b492cc776e3", type="any_subnet", - ip_family="dual", + ip_family="ipv4", is_vip=False, ) assert_matches_type(TaskIDList, reserved_fixed_ip, path=["response"]) @@ -481,7 +481,7 @@ async def test_method_create_with_all_params_overload_1(self, async_client: Asyn project_id=0, region_id=0, type="external", - ip_family="dual", + ip_family="ipv4", is_vip=False, ) assert_matches_type(TaskIDList, reserved_fixed_ip, path=["response"]) @@ -582,7 +582,7 @@ async def test_method_create_with_all_params_overload_3(self, async_client: Asyn region_id=0, network_id="e3c6ee77-48cb-416b-b204-11b492cc776e3", type="any_subnet", - ip_family="dual", + ip_family="ipv4", is_vip=False, ) assert_matches_type(TaskIDList, reserved_fixed_ip, path=["response"]) diff --git a/tests/api_resources/cloud/test_security_groups.py b/tests/api_resources/cloud/test_security_groups.py index b97cd265..8e06bd06 100644 --- a/tests/api_resources/cloud/test_security_groups.py +++ b/tests/api_resources/cloud/test_security_groups.py @@ -110,7 +110,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "remote_ip_prefix": "10.0.0.0/8", } ], - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(TaskIDList, security_group, path=["response"]) @@ -481,7 +484,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "remote_ip_prefix": "10.0.0.0/8", } ], - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(TaskIDList, security_group, path=["response"]) diff --git a/tests/api_resources/cloud/test_volume_snapshots.py b/tests/api_resources/cloud/test_volume_snapshots.py index 04ae487d..02ed69d4 100644 --- a/tests/api_resources/cloud/test_volume_snapshots.py +++ b/tests/api_resources/cloud/test_volume_snapshots.py @@ -88,7 +88,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=1, name="my-backup-snapshot", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Snapshot, volume_snapshot, path=["response"]) @@ -295,7 +298,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=1, region_id=1, name="my-backup-snapshot", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Snapshot, volume_snapshot, path=["response"]) diff --git a/tests/api_resources/cloud/test_volumes.py b/tests/api_resources/cloud/test_volumes.py index 1b58422a..0ea8f5a3 100644 --- a/tests/api_resources/cloud/test_volumes.py +++ b/tests/api_resources/cloud/test_volumes.py @@ -219,7 +219,10 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: project_id=1, region_id=1, name="some_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Volume, volume, path=["response"]) @@ -875,7 +878,10 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> project_id=1, region_id=1, name="some_name", - tags={"foo": "string"}, + tags={ + "my-tag": "my-tag-value", + "my-tag-to-remove": None, + }, ) assert_matches_type(Volume, volume, path=["response"]) From adc8f408f925862f13e19ac30c96684f40395aca Mon Sep 17 00:00:00 2001 From: Pedro Oliveira <8281907+pedrodeoliveira@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:02:15 +0100 Subject: [PATCH 13/19] feat(cloud): add delete_and_poll and examples for GPU baremetal clusters - Add missing delete_and_poll polling method for baremetal clusters (sync + async + RawResponse/Streaming wrappers) - Add runnable examples: gpu_baremetal_clusters.py and gpu_baremetal_clusters_async.py --- examples/cloud/gpu_baremetal_clusters.py | 190 +++++++++++++++++ .../cloud/gpu_baremetal_clusters_async.py | 193 ++++++++++++++++++ .../cloud/gpu_baremetal/clusters/clusters.py | 104 ++++++++++ 3 files changed, 487 insertions(+) create mode 100644 examples/cloud/gpu_baremetal_clusters.py create mode 100644 examples/cloud/gpu_baremetal_clusters_async.py diff --git a/examples/cloud/gpu_baremetal_clusters.py b/examples/cloud/gpu_baremetal_clusters.py new file mode 100644 index 00000000..ebb276cb --- /dev/null +++ b/examples/cloud/gpu_baremetal_clusters.py @@ -0,0 +1,190 @@ +from typing import List + +from gcore import Gcore +from gcore.types.cloud.gpu_image import GPUImage +from gcore.types.cloud.network_interface import NetworkInterface +from gcore.types.cloud.gpu_baremetal.cluster_create_params import ( + ServersSettings, + ServersSettingsInterfaceExternalInterfaceInputSerializer, +) +from gcore.types.cloud.gpu_baremetal.gpu_baremetal_cluster import GPUBaremetalCluster +from gcore.types.cloud.gpu_baremetal.clusters.gpu_baremetal_flavor import GPUBaremetalFlavor + + +def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + + gcore = Gcore( + # No need to explicitly pass to Gcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # Flavors + flavors = list_flavors(client=gcore) + + # Images + images = list_images(client=gcore) + + # Clusters + flavor_name = _get_first_available_flavor(flavors) + image_id = _get_first_image(images) + cluster = create_cluster(client=gcore, flavor=flavor_name, image_id=image_id) + get_cluster(client=gcore, cluster_id=cluster.id) + list_clusters(client=gcore) + update_cluster(client=gcore, cluster_id=cluster.id) + + # Interfaces + list_interfaces(client=gcore, cluster_id=cluster.id) + + # Servers + list_servers(client=gcore, cluster_id=cluster.id) + + # Delete + delete_cluster(client=gcore, cluster_id=cluster.id) + + +def create_cluster(*, client: Gcore, flavor: str, image_id: str) -> GPUBaremetalCluster: + print("\n=== CREATE GPU BAREMETAL CLUSTER ===") + cluster = client.cloud.gpu_baremetal.clusters.create_and_poll( + name="gcore-python-example-gpu-baremetal", + flavor=flavor, + image_id=image_id, + servers_count=1, + servers_settings=ServersSettings( + interfaces=[ + ServersSettingsInterfaceExternalInterfaceInputSerializer(type="external"), + ], + ), + tags={"name": "gcore-python-example"}, + ) + print(f"Created cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +def get_cluster(*, client: Gcore, cluster_id: str) -> GPUBaremetalCluster: + print("\n=== GET GPU BAREMETAL CLUSTER ===") + cluster = client.cloud.gpu_baremetal.clusters.get(cluster_id=cluster_id) + print(f"Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +def list_clusters(*, client: Gcore) -> None: + print("\n=== LIST GPU BAREMETAL CLUSTERS ===") + clusters = client.cloud.gpu_baremetal.clusters.list() + for count, cluster in enumerate(clusters, 1): + print(f" {count}. Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + + +def update_cluster(*, client: Gcore, cluster_id: str) -> None: + print("\n=== UPDATE GPU BAREMETAL CLUSTER ===") + cluster = client.cloud.gpu_baremetal.clusters.update( + cluster_id=cluster_id, name="gcore-python-example-gpu-baremetal-updated" + ) + print(f"Updated cluster: ID={cluster.id}, name={cluster.name}") + print("========================") + + +def delete_cluster(*, client: Gcore, cluster_id: str) -> None: + print("\n=== DELETE GPU BAREMETAL CLUSTER ===") + client.cloud.gpu_baremetal.clusters.delete_and_poll( + cluster_id=cluster_id, + all_floating_ips=True, + ) + print(f"Deleted cluster: ID={cluster_id}") + print("========================") + + +def list_interfaces(*, client: Gcore, cluster_id: str) -> List[NetworkInterface]: + print("\n=== LIST GPU BAREMETAL CLUSTER INTERFACES ===") + interfaces = client.cloud.gpu_baremetal.clusters.interfaces.list(cluster_id=cluster_id) + for count, interface in enumerate(interfaces.results, 1): + print(f" {count}. Interface: PortID={interface.port_id}, NetworkID={interface.network_id}") + print("========================") + return interfaces.results + + +def list_servers(*, client: Gcore, cluster_id: str) -> None: + print("\n=== LIST GPU BAREMETAL CLUSTER SERVERS ===") + servers = client.cloud.gpu_baremetal.clusters.servers.list(cluster_id=cluster_id) + for count, server in enumerate(servers.results, 1): + print(f" {count}. Server: ID={server.id}, name={server.name}, status={server.status}") + print("========================") + + +def list_flavors(*, client: Gcore) -> List[GPUBaremetalFlavor]: + print("\n=== LIST GPU BAREMETAL FLAVORS ===") + flavors = client.cloud.gpu_baremetal.clusters.flavors.list() + _print_flavor_details(flavors.results) + print("========================") + return flavors.results + + +def list_images(*, client: Gcore) -> List[GPUImage]: + print("\n=== LIST GPU BAREMETAL IMAGES ===") + images = client.cloud.gpu_baremetal.clusters.images.list() + _print_image_details(images.results) + print("========================") + return images.results + + +def _print_flavor_details(flavors: List[GPUBaremetalFlavor]) -> None: + display_count = 3 + if len(flavors) < display_count: + display_count = len(flavors) + + for i in range(display_count): + flavor = flavors[i] + print(f" {i + 1}. Flavor: name={flavor.name}") + print(f" Capacity: {flavor.capacity}") + status = "AVAILABLE" + if flavor.disabled: + status = "DISABLED" + print(f" Status: {status}") + print() + + if len(flavors) > display_count: + print(f" ... and {len(flavors) - display_count} more flavors") + + +def _print_image_details(images: List[GPUImage]) -> None: + display_count = 3 + if len(images) < display_count: + display_count = len(images) + + for i in range(display_count): + img = images[i] + print(f" {i + 1}. Image ID: {img.id}, name: {img.name}, OS type: {img.os_type}, status: {img.status}") + + if len(images) > display_count: + print(f" ... and {len(images) - display_count} more images") + + +def _get_first_available_flavor(flavors: List[GPUBaremetalFlavor]) -> str: + available_flavors = [f for f in flavors if not f.disabled and f.capacity > 0] + if not available_flavors: + raise ValueError("No available flavors with capacity found") + selected = available_flavors[0] + print(f"Selected flavor: {selected.name} (Capacity: {selected.capacity})") + return selected.name + + +def _get_first_image(images: List[GPUImage]) -> str: + if not images: + raise ValueError("No images found") + selected = images[0] + print(f"Selected image: {selected.name} (ID: {selected.id})") + return selected.id + + +if __name__ == "__main__": + main() diff --git a/examples/cloud/gpu_baremetal_clusters_async.py b/examples/cloud/gpu_baremetal_clusters_async.py new file mode 100644 index 00000000..44b88c4c --- /dev/null +++ b/examples/cloud/gpu_baremetal_clusters_async.py @@ -0,0 +1,193 @@ +import asyncio +from typing import List + +from gcore import AsyncGcore +from gcore.types.cloud.gpu_image import GPUImage +from gcore.types.cloud.network_interface import NetworkInterface +from gcore.types.cloud.gpu_baremetal.cluster_create_params import ( + ServersSettings, + ServersSettingsInterfaceExternalInterfaceInputSerializer, +) +from gcore.types.cloud.gpu_baremetal.gpu_baremetal_cluster import GPUBaremetalCluster +from gcore.types.cloud.gpu_baremetal.clusters.gpu_baremetal_flavor import GPUBaremetalFlavor + + +async def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + + gcore = AsyncGcore( + # No need to explicitly pass to AsyncGcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # Flavors + flavors = await list_flavors(client=gcore) + + # Images + images = await list_images(client=gcore) + + # Clusters + flavor_name = _get_first_available_flavor(flavors) + image_id = _get_first_image(images) + cluster = await create_cluster(client=gcore, flavor=flavor_name, image_id=image_id) + await get_cluster(client=gcore, cluster_id=cluster.id) + await list_clusters(client=gcore) + await update_cluster(client=gcore, cluster_id=cluster.id) + + # Interfaces + await list_interfaces(client=gcore, cluster_id=cluster.id) + + # Servers + await list_servers(client=gcore, cluster_id=cluster.id) + + # Delete + await delete_cluster(client=gcore, cluster_id=cluster.id) + + +async def create_cluster(*, client: AsyncGcore, flavor: str, image_id: str) -> GPUBaremetalCluster: + print("\n=== CREATE GPU BAREMETAL CLUSTER ===") + cluster = await client.cloud.gpu_baremetal.clusters.create_and_poll( + name="gcore-python-example-gpu-baremetal", + flavor=flavor, + image_id=image_id, + servers_count=1, + servers_settings=ServersSettings( + interfaces=[ + ServersSettingsInterfaceExternalInterfaceInputSerializer(type="external"), + ], + ), + tags={"name": "gcore-python-example"}, + ) + print(f"Created cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +async def get_cluster(*, client: AsyncGcore, cluster_id: str) -> GPUBaremetalCluster: + print("\n=== GET GPU BAREMETAL CLUSTER ===") + cluster = await client.cloud.gpu_baremetal.clusters.get(cluster_id=cluster_id) + print(f"Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +async def list_clusters(*, client: AsyncGcore) -> None: + print("\n=== LIST GPU BAREMETAL CLUSTERS ===") + clusters = await client.cloud.gpu_baremetal.clusters.list() + count = 0 + async for cluster in clusters: + count += 1 + print(f" {count}. Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + + +async def update_cluster(*, client: AsyncGcore, cluster_id: str) -> None: + print("\n=== UPDATE GPU BAREMETAL CLUSTER ===") + cluster = await client.cloud.gpu_baremetal.clusters.update( + cluster_id=cluster_id, name="gcore-python-example-gpu-baremetal-updated" + ) + print(f"Updated cluster: ID={cluster.id}, name={cluster.name}") + print("========================") + + +async def delete_cluster(*, client: AsyncGcore, cluster_id: str) -> None: + print("\n=== DELETE GPU BAREMETAL CLUSTER ===") + await client.cloud.gpu_baremetal.clusters.delete_and_poll( + cluster_id=cluster_id, + all_floating_ips=True, + ) + print(f"Deleted cluster: ID={cluster_id}") + print("========================") + + +async def list_interfaces(*, client: AsyncGcore, cluster_id: str) -> List[NetworkInterface]: + print("\n=== LIST GPU BAREMETAL CLUSTER INTERFACES ===") + interfaces = await client.cloud.gpu_baremetal.clusters.interfaces.list(cluster_id=cluster_id) + for count, interface in enumerate(interfaces.results, 1): + print(f" {count}. Interface: PortID={interface.port_id}, NetworkID={interface.network_id}") + print("========================") + return interfaces.results + + +async def list_servers(*, client: AsyncGcore, cluster_id: str) -> None: + print("\n=== LIST GPU BAREMETAL CLUSTER SERVERS ===") + servers = await client.cloud.gpu_baremetal.clusters.servers.list(cluster_id=cluster_id) + for count, server in enumerate(servers.results, 1): + print(f" {count}. Server: ID={server.id}, name={server.name}, status={server.status}") + print("========================") + + +async def list_flavors(*, client: AsyncGcore) -> List[GPUBaremetalFlavor]: + print("\n=== LIST GPU BAREMETAL FLAVORS ===") + flavors = await client.cloud.gpu_baremetal.clusters.flavors.list() + _print_flavor_details(flavors.results) + print("========================") + return flavors.results + + +async def list_images(*, client: AsyncGcore) -> List[GPUImage]: + print("\n=== LIST GPU BAREMETAL IMAGES ===") + images = await client.cloud.gpu_baremetal.clusters.images.list() + _print_image_details(images.results) + print("========================") + return images.results + + +def _print_flavor_details(flavors: List[GPUBaremetalFlavor]) -> None: + display_count = 3 + if len(flavors) < display_count: + display_count = len(flavors) + + for i in range(display_count): + flavor = flavors[i] + print(f" {i + 1}. Flavor: name={flavor.name}") + print(f" Capacity: {flavor.capacity}") + status = "AVAILABLE" + if flavor.disabled: + status = "DISABLED" + print(f" Status: {status}") + print() + + if len(flavors) > display_count: + print(f" ... and {len(flavors) - display_count} more flavors") + + +def _print_image_details(images: List[GPUImage]) -> None: + display_count = 3 + if len(images) < display_count: + display_count = len(images) + + for i in range(display_count): + img = images[i] + print(f" {i + 1}. Image ID: {img.id}, name: {img.name}, OS type: {img.os_type}, status: {img.status}") + + if len(images) > display_count: + print(f" ... and {len(images) - display_count} more images") + + +def _get_first_available_flavor(flavors: List[GPUBaremetalFlavor]) -> str: + available_flavors = [f for f in flavors if not f.disabled and f.capacity > 0] + if not available_flavors: + raise ValueError("No available flavors with capacity found") + selected = available_flavors[0] + print(f"Selected flavor: {selected.name} (Capacity: {selected.capacity})") + return selected.name + + +def _get_first_image(images: List[GPUImage]) -> str: + if not images: + raise ValueError("No images found") + selected = images[0] + print(f"Selected image: {selected.name} (ID: {selected.id})") + return selected.id + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py b/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py index cfd9a4a1..74a1b4a2 100644 --- a/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py +++ b/src/gcore/resources/cloud/gpu_baremetal/clusters/clusters.py @@ -885,6 +885,52 @@ def resize_and_poll( timeout=timeout, ) + def delete_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + all_floating_ips: bool | Omit = omit, + all_reserved_fixed_ips: bool | Omit = omit, + floating_ip_ids: SequenceNotStr[str] | Omit = omit, + reserved_fixed_ip_ids: SequenceNotStr[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, + ) -> None: + """ + Delete 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 = self.delete( + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + all_floating_ips=all_floating_ips, + all_reserved_fixed_ips=all_reserved_fixed_ips, + floating_ip_ids=floating_ip_ids, + reserved_fixed_ip_ids=reserved_fixed_ip_ids, + 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 AsyncClustersResource(AsyncAPIResource): @cached_property @@ -1705,6 +1751,52 @@ async def resize_and_poll( timeout=timeout, ) + async def delete_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + all_floating_ips: bool | Omit = omit, + all_reserved_fixed_ips: bool | Omit = omit, + floating_ip_ids: SequenceNotStr[str] | Omit = omit, + reserved_fixed_ip_ids: SequenceNotStr[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, + ) -> None: + """ + Delete 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.delete( + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + all_floating_ips=all_floating_ips, + all_reserved_fixed_ips=all_reserved_fixed_ips, + floating_ip_ids=floating_ip_ids, + reserved_fixed_ip_ids=reserved_fixed_ip_ids, + 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 ClustersResourceWithRawResponse: def __init__(self, clusters: ClustersResource) -> None: @@ -1753,6 +1845,9 @@ def __init__(self, clusters: ClustersResource) -> None: self.resize_and_poll = to_raw_response_wrapper( clusters.resize_and_poll, ) + self.delete_and_poll = to_raw_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def interfaces(self) -> InterfacesResourceWithRawResponse: @@ -1819,6 +1914,9 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.resize_and_poll = async_to_raw_response_wrapper( clusters.resize_and_poll, ) + self.delete_and_poll = async_to_raw_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def interfaces(self) -> AsyncInterfacesResourceWithRawResponse: @@ -1885,6 +1983,9 @@ def __init__(self, clusters: ClustersResource) -> None: self.resize_and_poll = to_streamed_response_wrapper( clusters.resize_and_poll, ) + self.delete_and_poll = to_streamed_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def interfaces(self) -> InterfacesResourceWithStreamingResponse: @@ -1951,6 +2052,9 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.resize_and_poll = async_to_streamed_response_wrapper( clusters.resize_and_poll, ) + self.delete_and_poll = async_to_streamed_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def interfaces(self) -> AsyncInterfacesResourceWithStreamingResponse: From 1ed9b85b772bd52971503e21a7e484af6812f5b4 Mon Sep 17 00:00:00 2001 From: Pedro Oliveira <8281907+pedrodeoliveira@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:05:16 +0100 Subject: [PATCH 14/19] feat(cloud): add polling methods and examples for GPU virtual clusters - Add polling methods for GPU virtual cluster resources: - clusters: create_and_poll, delete_and_poll, action_and_poll - images: delete_and_poll, upload_and_poll - servers: delete_and_poll - Add runnable examples: gpu_virtual_clusters.py and gpu_virtual_clusters_async.py --- examples/cloud/gpu_virtual_clusters.py | 188 ++++++ examples/cloud/gpu_virtual_clusters_async.py | 191 ++++++ .../cloud/gpu_virtual/clusters/clusters.py | 574 ++++++++++++++++++ .../cloud/gpu_virtual/clusters/images.py | 236 +++++++ .../cloud/gpu_virtual/clusters/servers.py | 116 ++++ 5 files changed, 1305 insertions(+) create mode 100644 examples/cloud/gpu_virtual_clusters.py create mode 100644 examples/cloud/gpu_virtual_clusters_async.py diff --git a/examples/cloud/gpu_virtual_clusters.py b/examples/cloud/gpu_virtual_clusters.py new file mode 100644 index 00000000..16a713d6 --- /dev/null +++ b/examples/cloud/gpu_virtual_clusters.py @@ -0,0 +1,188 @@ +from typing import List + +from gcore import Gcore +from gcore.types.cloud.gpu_image import GPUImage +from gcore.types.cloud.gpu_virtual.gpu_virtual_cluster import GPUVirtualCluster +from gcore.types.cloud.gpu_virtual.cluster_create_params import ( + ServersSettings, + ServersSettingsVolumeImageVolumeInputSerializer, + ServersSettingsInterfaceExternalInterfaceInputSerializer, +) +from gcore.types.cloud.gpu_virtual.clusters.gpu_virtual_flavor import GPUVirtualFlavor + + +def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + + gcore = Gcore( + # No need to explicitly pass to Gcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # Flavors + flavors = list_flavors(client=gcore) + + # Images + images = list_images(client=gcore) + + # Clusters + flavor_name = _get_first_available_flavor(flavors) + image_id = _get_first_image(images) + cluster = create_cluster(client=gcore, flavor=flavor_name, image_id=image_id) + get_cluster(client=gcore, cluster_id=cluster.id) + list_clusters(client=gcore) + update_cluster(client=gcore, cluster_id=cluster.id) + + # Servers + list_servers(client=gcore, cluster_id=cluster.id) + + # Delete + delete_cluster(client=gcore, cluster_id=cluster.id) + + +def create_cluster(*, client: Gcore, flavor: str, image_id: str) -> GPUVirtualCluster: + print("\n=== CREATE GPU VIRTUAL CLUSTER ===") + cluster = client.cloud.gpu_virtual.clusters.create_and_poll( + name="gcore-python-example-gpu-virtual", + flavor=flavor, + servers_count=1, + servers_settings=ServersSettings( + interfaces=[ + ServersSettingsInterfaceExternalInterfaceInputSerializer(type="external"), + ], + volumes=[ + ServersSettingsVolumeImageVolumeInputSerializer( + name="gcore-python-example-volume", + size=50, + type="standard", + source="image", + image_id=image_id, + boot_index=0, + ), + ], + ), + tags={"name": "gcore-python-example"}, + ) + print(f"Created cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +def get_cluster(*, client: Gcore, cluster_id: str) -> GPUVirtualCluster: + print("\n=== GET GPU VIRTUAL CLUSTER ===") + cluster = client.cloud.gpu_virtual.clusters.get(cluster_id=cluster_id) + print(f"Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +def list_clusters(*, client: Gcore) -> None: + print("\n=== LIST GPU VIRTUAL CLUSTERS ===") + clusters = client.cloud.gpu_virtual.clusters.list() + for count, cluster in enumerate(clusters, 1): + print(f" {count}. Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + + +def update_cluster(*, client: Gcore, cluster_id: str) -> None: + print("\n=== UPDATE GPU VIRTUAL CLUSTER ===") + cluster = client.cloud.gpu_virtual.clusters.update( + cluster_id=cluster_id, name="gcore-python-example-gpu-virtual-updated" + ) + print(f"Updated cluster: ID={cluster.id}, name={cluster.name}") + print("========================") + + +def delete_cluster(*, client: Gcore, cluster_id: str) -> None: + print("\n=== DELETE GPU VIRTUAL CLUSTER ===") + client.cloud.gpu_virtual.clusters.delete_and_poll( + cluster_id=cluster_id, + all_volumes=True, + all_floating_ips=True, + ) + print(f"Deleted cluster: ID={cluster_id}") + print("========================") + + +def list_servers(*, client: Gcore, cluster_id: str) -> None: + print("\n=== LIST GPU VIRTUAL CLUSTER SERVERS ===") + servers = client.cloud.gpu_virtual.clusters.servers.list(cluster_id=cluster_id) + for count, server in enumerate(servers.results, 1): + print(f" {count}. Server: ID={server.id}, name={server.name}, status={server.status}") + print("========================") + + +def list_flavors(*, client: Gcore) -> List[GPUVirtualFlavor]: + print("\n=== LIST GPU VIRTUAL FLAVORS ===") + flavors = client.cloud.gpu_virtual.clusters.flavors.list() + _print_flavor_details(flavors.results) + print("========================") + return flavors.results + + +def list_images(*, client: Gcore) -> List[GPUImage]: + print("\n=== LIST GPU VIRTUAL IMAGES ===") + images = client.cloud.gpu_virtual.clusters.images.list() + _print_image_details(images.results) + print("========================") + return images.results + + +def _print_flavor_details(flavors: List[GPUVirtualFlavor]) -> None: + display_count = 3 + if len(flavors) < display_count: + display_count = len(flavors) + + for i in range(display_count): + flavor = flavors[i] + print(f" {i + 1}. Flavor: name={flavor.name}") + print(f" Capacity: {flavor.capacity}") + status = "AVAILABLE" + if flavor.disabled: + status = "DISABLED" + print(f" Status: {status}") + print() + + if len(flavors) > display_count: + print(f" ... and {len(flavors) - display_count} more flavors") + + +def _print_image_details(images: List[GPUImage]) -> None: + display_count = 3 + if len(images) < display_count: + display_count = len(images) + + for i in range(display_count): + img = images[i] + print(f" {i + 1}. Image ID: {img.id}, name: {img.name}, OS type: {img.os_type}, status: {img.status}") + + if len(images) > display_count: + print(f" ... and {len(images) - display_count} more images") + + +def _get_first_available_flavor(flavors: List[GPUVirtualFlavor]) -> str: + available_flavors = [f for f in flavors if not f.disabled and f.capacity > 0] + if not available_flavors: + raise ValueError("No available flavors with capacity found") + selected = available_flavors[0] + print(f"Selected flavor: {selected.name} (Capacity: {selected.capacity})") + return selected.name + + +def _get_first_image(images: List[GPUImage]) -> str: + if not images: + raise ValueError("No images found") + selected = images[0] + print(f"Selected image: {selected.name} (ID: {selected.id})") + return selected.id + + +if __name__ == "__main__": + main() diff --git a/examples/cloud/gpu_virtual_clusters_async.py b/examples/cloud/gpu_virtual_clusters_async.py new file mode 100644 index 00000000..409d8d68 --- /dev/null +++ b/examples/cloud/gpu_virtual_clusters_async.py @@ -0,0 +1,191 @@ +import asyncio +from typing import List + +from gcore import AsyncGcore +from gcore.types.cloud.gpu_image import GPUImage +from gcore.types.cloud.gpu_virtual.gpu_virtual_cluster import GPUVirtualCluster +from gcore.types.cloud.gpu_virtual.cluster_create_params import ( + ServersSettings, + ServersSettingsVolumeImageVolumeInputSerializer, + ServersSettingsInterfaceExternalInterfaceInputSerializer, +) +from gcore.types.cloud.gpu_virtual.clusters.gpu_virtual_flavor import GPUVirtualFlavor + + +async def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + + gcore = AsyncGcore( + # No need to explicitly pass to AsyncGcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # Flavors + flavors = await list_flavors(client=gcore) + + # Images + images = await list_images(client=gcore) + + # Clusters + flavor_name = _get_first_available_flavor(flavors) + image_id = _get_first_image(images) + cluster = await create_cluster(client=gcore, flavor=flavor_name, image_id=image_id) + await get_cluster(client=gcore, cluster_id=cluster.id) + await list_clusters(client=gcore) + await update_cluster(client=gcore, cluster_id=cluster.id) + + # Servers + await list_servers(client=gcore, cluster_id=cluster.id) + + # Delete + await delete_cluster(client=gcore, cluster_id=cluster.id) + + +async def create_cluster(*, client: AsyncGcore, flavor: str, image_id: str) -> GPUVirtualCluster: + print("\n=== CREATE GPU VIRTUAL CLUSTER ===") + cluster = await client.cloud.gpu_virtual.clusters.create_and_poll( + name="gcore-python-example-gpu-virtual", + flavor=flavor, + servers_count=1, + servers_settings=ServersSettings( + interfaces=[ + ServersSettingsInterfaceExternalInterfaceInputSerializer(type="external"), + ], + volumes=[ + ServersSettingsVolumeImageVolumeInputSerializer( + name="gcore-python-example-volume", + size=50, + type="standard", + source="image", + image_id=image_id, + boot_index=0, + ), + ], + ), + tags={"name": "gcore-python-example"}, + ) + print(f"Created cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +async def get_cluster(*, client: AsyncGcore, cluster_id: str) -> GPUVirtualCluster: + print("\n=== GET GPU VIRTUAL CLUSTER ===") + cluster = await client.cloud.gpu_virtual.clusters.get(cluster_id=cluster_id) + print(f"Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + return cluster + + +async def list_clusters(*, client: AsyncGcore) -> None: + print("\n=== LIST GPU VIRTUAL CLUSTERS ===") + clusters = await client.cloud.gpu_virtual.clusters.list() + count = 0 + async for cluster in clusters: + count += 1 + print(f" {count}. Cluster: ID={cluster.id}, name={cluster.name}, status={cluster.status}") + print("========================") + + +async def update_cluster(*, client: AsyncGcore, cluster_id: str) -> None: + print("\n=== UPDATE GPU VIRTUAL CLUSTER ===") + cluster = await client.cloud.gpu_virtual.clusters.update( + cluster_id=cluster_id, name="gcore-python-example-gpu-virtual-updated" + ) + print(f"Updated cluster: ID={cluster.id}, name={cluster.name}") + print("========================") + + +async def delete_cluster(*, client: AsyncGcore, cluster_id: str) -> None: + print("\n=== DELETE GPU VIRTUAL CLUSTER ===") + await client.cloud.gpu_virtual.clusters.delete_and_poll( + cluster_id=cluster_id, + all_volumes=True, + all_floating_ips=True, + ) + print(f"Deleted cluster: ID={cluster_id}") + print("========================") + + +async def list_servers(*, client: AsyncGcore, cluster_id: str) -> None: + print("\n=== LIST GPU VIRTUAL CLUSTER SERVERS ===") + servers = await client.cloud.gpu_virtual.clusters.servers.list(cluster_id=cluster_id) + for count, server in enumerate(servers.results, 1): + print(f" {count}. Server: ID={server.id}, name={server.name}, status={server.status}") + print("========================") + + +async def list_flavors(*, client: AsyncGcore) -> List[GPUVirtualFlavor]: + print("\n=== LIST GPU VIRTUAL FLAVORS ===") + flavors = await client.cloud.gpu_virtual.clusters.flavors.list() + _print_flavor_details(flavors.results) + print("========================") + return flavors.results + + +async def list_images(*, client: AsyncGcore) -> List[GPUImage]: + print("\n=== LIST GPU VIRTUAL IMAGES ===") + images = await client.cloud.gpu_virtual.clusters.images.list() + _print_image_details(images.results) + print("========================") + return images.results + + +def _print_flavor_details(flavors: List[GPUVirtualFlavor]) -> None: + display_count = 3 + if len(flavors) < display_count: + display_count = len(flavors) + + for i in range(display_count): + flavor = flavors[i] + print(f" {i + 1}. Flavor: name={flavor.name}") + print(f" Capacity: {flavor.capacity}") + status = "AVAILABLE" + if flavor.disabled: + status = "DISABLED" + print(f" Status: {status}") + print() + + if len(flavors) > display_count: + print(f" ... and {len(flavors) - display_count} more flavors") + + +def _print_image_details(images: List[GPUImage]) -> None: + display_count = 3 + if len(images) < display_count: + display_count = len(images) + + for i in range(display_count): + img = images[i] + print(f" {i + 1}. Image ID: {img.id}, name: {img.name}, OS type: {img.os_type}, status: {img.status}") + + if len(images) > display_count: + print(f" ... and {len(images) - display_count} more images") + + +def _get_first_available_flavor(flavors: List[GPUVirtualFlavor]) -> str: + available_flavors = [f for f in flavors if not f.disabled and f.capacity > 0] + if not available_flavors: + raise ValueError("No available flavors with capacity found") + selected = available_flavors[0] + print(f"Selected flavor: {selected.name} (Capacity: {selected.capacity})") + return selected.name + + +def _get_first_image(images: List[GPUImage]) -> str: + if not images: + raise ValueError("No images found") + selected = images[0] + print(f"Selected image: {selected.name} (ID: {selected.id})") + return selected.id + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/gcore/resources/cloud/gpu_virtual/clusters/clusters.py b/src/gcore/resources/cloud/gpu_virtual/clusters/clusters.py index a5647c60..5af28edf 100644 --- a/src/gcore/resources/cloud/gpu_virtual/clusters/clusters.py +++ b/src/gcore/resources/cloud/gpu_virtual/clusters/clusters.py @@ -714,6 +714,275 @@ def get( cast_to=GPUVirtualCluster, ) + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + flavor: 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, + ) -> GPUVirtualCluster: + """ + Create a virtual GPU cluster and wait for it to be ready. + """ + response = self.create( + project_id=project_id, + region_id=region_id, + flavor=flavor, + 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 = 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 self.get( + 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, + ) + + def delete_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + all_floating_ips: bool | Omit = omit, + all_reserved_fixed_ips: bool | Omit = omit, + all_volumes: bool | Omit = omit, + floating_ip_ids: SequenceNotStr[str] | Omit = omit, + reserved_fixed_ip_ids: SequenceNotStr[str] | Omit = omit, + volume_ids: SequenceNotStr[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, + ) -> None: + """ + Delete a virtual 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 = self.delete( + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + all_floating_ips=all_floating_ips, + all_reserved_fixed_ips=all_reserved_fixed_ips, + all_volumes=all_volumes, + floating_ip_ids=floating_ip_ids, + reserved_fixed_ip_ids=reserved_fixed_ip_ids, + volume_ids=volume_ids, + 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, + ) + + @overload + def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["start"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["stop"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["soft_reboot"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["hard_reboot"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["resize"], + servers_count: int, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @required_args(["action"], ["action", "servers_count"]) + def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["start"] + | Literal["stop"] + | Literal["soft_reboot"] + | Literal["hard_reboot"] + | Literal["resize"], + servers_count: int | 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, + ) -> GPUVirtualCluster: + """ + Perform an action on a virtual 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. + """ + 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}") + response = self._post( + path_template( + "/cloud/v3/gpu/virtual/{project_id}/{region_id}/clusters/{cluster_id}/action", + project_id=project_id, + region_id=region_id, + cluster_id=cluster_id, + ), + body=maybe_transform( + { + "action": action, + "servers_count": servers_count, + }, + cluster_action_params.ClusterActionParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TaskIDList, + ) + if not response.tasks: + 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, + ) + return self.get( + 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 AsyncClustersResource(AsyncAPIResource): """ @@ -1356,6 +1625,275 @@ async def get( cast_to=GPUVirtualCluster, ) + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + flavor: 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, + ) -> GPUVirtualCluster: + """ + Create a virtual GPU cluster and wait for it to be ready. + """ + response = await self.create( + project_id=project_id, + region_id=region_id, + flavor=flavor, + 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( + 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 delete_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + all_floating_ips: bool | Omit = omit, + all_reserved_fixed_ips: bool | Omit = omit, + all_volumes: bool | Omit = omit, + floating_ip_ids: SequenceNotStr[str] | Omit = omit, + reserved_fixed_ip_ids: SequenceNotStr[str] | Omit = omit, + volume_ids: SequenceNotStr[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, + ) -> None: + """ + Delete a virtual 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.delete( + cluster_id=cluster_id, + project_id=project_id, + region_id=region_id, + all_floating_ips=all_floating_ips, + all_reserved_fixed_ips=all_reserved_fixed_ips, + all_volumes=all_volumes, + floating_ip_ids=floating_ip_ids, + reserved_fixed_ip_ids=reserved_fixed_ip_ids, + volume_ids=volume_ids, + 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, + ) + + @overload + async def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["start"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + async def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["stop"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + async def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["soft_reboot"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + async def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["hard_reboot"], + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @overload + async def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["resize"], + servers_count: int, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GPUVirtualCluster: + """Perform an action on a virtual 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.""" + ... + + @required_args(["action"], ["action", "servers_count"]) + async def action_and_poll( + self, + cluster_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + action: Literal["start"] + | Literal["stop"] + | Literal["soft_reboot"] + | Literal["hard_reboot"] + | Literal["resize"], + servers_count: int | 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, + ) -> GPUVirtualCluster: + """ + Perform an action on a virtual 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. + """ + 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}") + response = await self._post( + path_template( + "/cloud/v3/gpu/virtual/{project_id}/{region_id}/clusters/{cluster_id}/action", + project_id=project_id, + region_id=region_id, + cluster_id=cluster_id, + ), + body=await async_maybe_transform( + { + "action": action, + "servers_count": servers_count, + }, + cluster_action_params.ClusterActionParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TaskIDList, + ) + 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( + 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: def __init__(self, clusters: ClustersResource) -> None: @@ -1379,6 +1917,15 @@ def __init__(self, clusters: ClustersResource) -> None: self.get = to_raw_response_wrapper( clusters.get, ) + self.create_and_poll = to_raw_response_wrapper( + clusters.create_and_poll, + ) + self.delete_and_poll = to_raw_response_wrapper( + clusters.delete_and_poll, + ) + self.action_and_poll = to_raw_response_wrapper( + clusters.action_and_poll, + ) @cached_property def servers(self) -> ServersResourceWithRawResponse: @@ -1424,6 +1971,15 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.get = async_to_raw_response_wrapper( clusters.get, ) + self.create_and_poll = async_to_raw_response_wrapper( + clusters.create_and_poll, + ) + self.delete_and_poll = async_to_raw_response_wrapper( + clusters.delete_and_poll, + ) + self.action_and_poll = async_to_raw_response_wrapper( + clusters.action_and_poll, + ) @cached_property def servers(self) -> AsyncServersResourceWithRawResponse: @@ -1469,6 +2025,15 @@ def __init__(self, clusters: ClustersResource) -> None: self.get = to_streamed_response_wrapper( clusters.get, ) + self.create_and_poll = to_streamed_response_wrapper( + clusters.create_and_poll, + ) + self.delete_and_poll = to_streamed_response_wrapper( + clusters.delete_and_poll, + ) + self.action_and_poll = to_streamed_response_wrapper( + clusters.action_and_poll, + ) @cached_property def servers(self) -> ServersResourceWithStreamingResponse: @@ -1514,6 +2079,15 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.get = async_to_streamed_response_wrapper( clusters.get, ) + self.create_and_poll = async_to_streamed_response_wrapper( + clusters.create_and_poll, + ) + self.delete_and_poll = async_to_streamed_response_wrapper( + clusters.delete_and_poll, + ) + self.action_and_poll = async_to_streamed_response_wrapper( + clusters.action_and_poll, + ) @cached_property def servers(self) -> AsyncServersResourceWithStreamingResponse: diff --git a/src/gcore/resources/cloud/gpu_virtual/clusters/images.py b/src/gcore/resources/cloud/gpu_virtual/clusters/images.py index f68f2526..1b73fea6 100644 --- a/src/gcore/resources/cloud/gpu_virtual/clusters/images.py +++ b/src/gcore/resources/cloud/gpu_virtual/clusters/images.py @@ -283,6 +283,112 @@ def upload( cast_to=TaskIDList, ) + def delete_and_poll( + self, + image_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, + ) -> None: + """ + Delete a virtual GPU image 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( + image_id=image_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 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 upload_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + name: str, + url: str, + architecture: Optional[Literal["aarch64", "x86_64"]] | Omit = omit, + cow_format: bool | Omit = omit, + hw_firmware_type: Optional[Literal["bios", "uefi"]] | Omit = omit, + os_distro: Optional[str] | Omit = omit, + os_type: Optional[Literal["linux", "windows"]] | Omit = omit, + os_version: Optional[str] | Omit = omit, + ssh_key: Literal["allow", "deny", "required"] | Omit = omit, + 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, + ) -> GPUImage: + """ + Upload a new virtual GPU image and wait for the upload to complete. + """ + response = self.upload( + project_id=project_id, + region_id=region_id, + name=name, + url=url, + architecture=architecture, + cow_format=cow_format, + hw_firmware_type=hw_firmware_type, + os_distro=os_distro, + os_type=os_type, + os_version=os_version, + ssh_key=ssh_key, + 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 = 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.images: + raise ValueError("No image was created") + image_id = task.created_resources.images[0] + return self.get( + image_id=image_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + class AsyncImagesResource(AsyncAPIResource): """GPU virtual images are custom boot images for virtual GPU cluster instances.""" @@ -541,6 +647,112 @@ async def upload( cast_to=TaskIDList, ) + async def delete_and_poll( + self, + image_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, + ) -> None: + """ + Delete a virtual GPU image 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( + image_id=image_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 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 upload_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + name: str, + url: str, + architecture: Optional[Literal["aarch64", "x86_64"]] | Omit = omit, + cow_format: bool | Omit = omit, + hw_firmware_type: Optional[Literal["bios", "uefi"]] | Omit = omit, + os_distro: Optional[str] | Omit = omit, + os_type: Optional[Literal["linux", "windows"]] | Omit = omit, + os_version: Optional[str] | Omit = omit, + ssh_key: Literal["allow", "deny", "required"] | Omit = omit, + 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, + ) -> GPUImage: + """ + Upload a new virtual GPU image and wait for the upload to complete. + """ + response = await self.upload( + project_id=project_id, + region_id=region_id, + name=name, + url=url, + architecture=architecture, + cow_format=cow_format, + hw_firmware_type=hw_firmware_type, + os_distro=os_distro, + os_type=os_type, + os_version=os_version, + ssh_key=ssh_key, + 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.images: + raise ValueError("No image was created") + image_id = task.created_resources.images[0] + return await self.get( + image_id=image_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + class ImagesResourceWithRawResponse: def __init__(self, images: ImagesResource) -> None: @@ -558,6 +770,12 @@ def __init__(self, images: ImagesResource) -> None: self.upload = to_raw_response_wrapper( images.upload, ) + self.delete_and_poll = to_raw_response_wrapper( + images.delete_and_poll, + ) + self.upload_and_poll = to_raw_response_wrapper( + images.upload_and_poll, + ) class AsyncImagesResourceWithRawResponse: @@ -576,6 +794,12 @@ def __init__(self, images: AsyncImagesResource) -> None: self.upload = async_to_raw_response_wrapper( images.upload, ) + self.delete_and_poll = async_to_raw_response_wrapper( + images.delete_and_poll, + ) + self.upload_and_poll = async_to_raw_response_wrapper( + images.upload_and_poll, + ) class ImagesResourceWithStreamingResponse: @@ -594,6 +818,12 @@ def __init__(self, images: ImagesResource) -> None: self.upload = to_streamed_response_wrapper( images.upload, ) + self.delete_and_poll = to_streamed_response_wrapper( + images.delete_and_poll, + ) + self.upload_and_poll = to_streamed_response_wrapper( + images.upload_and_poll, + ) class AsyncImagesResourceWithStreamingResponse: @@ -612,3 +842,9 @@ def __init__(self, images: AsyncImagesResource) -> None: self.upload = async_to_streamed_response_wrapper( images.upload, ) + self.delete_and_poll = async_to_streamed_response_wrapper( + images.delete_and_poll, + ) + self.upload_and_poll = async_to_streamed_response_wrapper( + images.upload_and_poll, + ) diff --git a/src/gcore/resources/cloud/gpu_virtual/clusters/servers.py b/src/gcore/resources/cloud/gpu_virtual/clusters/servers.py index 08683b79..f7c47c61 100644 --- a/src/gcore/resources/cloud/gpu_virtual/clusters/servers.py +++ b/src/gcore/resources/cloud/gpu_virtual/clusters/servers.py @@ -252,6 +252,58 @@ def delete( cast_to=TaskIDList, ) + def delete_and_poll( + self, + server_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_id: str, + all_floating_ips: bool | Omit = omit, + all_reserved_fixed_ips: bool | Omit = omit, + all_volumes: bool | Omit = omit, + floating_ip_ids: SequenceNotStr[str] | Omit = omit, + reserved_fixed_ip_ids: SequenceNotStr[str] | Omit = omit, + volume_ids: SequenceNotStr[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, + ) -> None: + """ + Delete a server from a virtual 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 = self.delete( + server_id=server_id, + project_id=project_id, + region_id=region_id, + cluster_id=cluster_id, + all_floating_ips=all_floating_ips, + all_reserved_fixed_ips=all_reserved_fixed_ips, + all_volumes=all_volumes, + floating_ip_ids=floating_ip_ids, + reserved_fixed_ip_ids=reserved_fixed_ip_ids, + volume_ids=volume_ids, + 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 @@ -479,6 +531,58 @@ async def delete( cast_to=TaskIDList, ) + async def delete_and_poll( + self, + server_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_id: str, + all_floating_ips: bool | Omit = omit, + all_reserved_fixed_ips: bool | Omit = omit, + all_volumes: bool | Omit = omit, + floating_ip_ids: SequenceNotStr[str] | Omit = omit, + reserved_fixed_ip_ids: SequenceNotStr[str] | Omit = omit, + volume_ids: SequenceNotStr[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, + ) -> None: + """ + Delete a server from a virtual 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.delete( + server_id=server_id, + project_id=project_id, + region_id=region_id, + cluster_id=cluster_id, + all_floating_ips=all_floating_ips, + all_reserved_fixed_ips=all_reserved_fixed_ips, + all_volumes=all_volumes, + floating_ip_ids=floating_ip_ids, + reserved_fixed_ip_ids=reserved_fixed_ip_ids, + volume_ids=volume_ids, + 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: @@ -490,6 +594,9 @@ def __init__(self, servers: ServersResource) -> None: self.delete = to_raw_response_wrapper( servers.delete, ) + self.delete_and_poll = to_raw_response_wrapper( + servers.delete_and_poll, + ) class AsyncServersResourceWithRawResponse: @@ -502,6 +609,9 @@ def __init__(self, servers: AsyncServersResource) -> None: self.delete = async_to_raw_response_wrapper( servers.delete, ) + self.delete_and_poll = async_to_raw_response_wrapper( + servers.delete_and_poll, + ) class ServersResourceWithStreamingResponse: @@ -514,6 +624,9 @@ def __init__(self, servers: ServersResource) -> None: self.delete = to_streamed_response_wrapper( servers.delete, ) + self.delete_and_poll = to_streamed_response_wrapper( + servers.delete_and_poll, + ) class AsyncServersResourceWithStreamingResponse: @@ -526,3 +639,6 @@ def __init__(self, servers: AsyncServersResource) -> None: self.delete = async_to_streamed_response_wrapper( servers.delete, ) + self.delete_and_poll = async_to_streamed_response_wrapper( + servers.delete_and_poll, + ) From 51ad9c97608b81a0358c3c8e06b3321cb331a06b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 08:45:00 +0000 Subject: [PATCH 15/19] feat(api): aggregated API specs update --- .stats.yml | 6 +- src/gcore/resources/cloud/networks/routers.py | 32 +++++++-- .../clusters/gpu_baremetal_cluster_server.py | 3 + .../gpu_baremetal/gpu_baremetal_cluster.py | 5 ++ .../clusters/gpu_virtual_cluster_server.py | 3 + .../cloud/gpu_virtual/gpu_virtual_cluster.py | 5 ++ .../cloud/networks/router_create_params.py | 2 + .../cloud/networks/router_list_params.py | 9 ++- .../cloud/networks/test_routers.py | 70 ++++++++++--------- 9 files changed, 92 insertions(+), 43 deletions(-) diff --git a/.stats.yml b/.stats.yml index febda490..34273db4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-13b4a16cd43e7638a0158083372e57034eff7a5024c83e70a7673823bad77b34.yml -openapi_spec_hash: 6b0a3adf49edd6629275fc3a033c391b -config_hash: a8091c31251bc8979abd6db1f2c88983 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-c0227d112d00ea1367115bffc78528aae50c3bef4b763b67f291c20da36e759c.yml +openapi_spec_hash: 728075186aa56e84ef78f6df633242e7 +config_hash: 1eeaf5413c25f32913ceebe18d37cc3e diff --git a/src/gcore/resources/cloud/networks/routers.py b/src/gcore/resources/cloud/networks/routers.py index 56931a61..8946358b 100644 --- a/src/gcore/resources/cloud/networks/routers.py +++ b/src/gcore/resources/cloud/networks/routers.py @@ -76,6 +76,10 @@ def create( Create a new router with the specified configuration. Args: + project_id: Project ID + + region_id: Region ID + name: name of router external_gateway_info: External gateway configuration. Use type 'default' to let the platform @@ -188,6 +192,7 @@ def list( project_id: int | None = None, region_id: int | None = None, limit: int | Omit = omit, + name: str | Omit = omit, offset: 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. @@ -200,9 +205,15 @@ def list( List all routers in the specified project and region. Args: - limit: Limit the number of returned routers + project_id: Project ID + + region_id: Region ID + + limit: Limit of items on a single page + + name: Optional. Filter routers by name - offset: Offset value is used to exclude the first set of records from the result + offset: Offset in results list extra_headers: Send extra headers @@ -227,6 +238,7 @@ def list( query=maybe_transform( { "limit": limit, + "name": name, "offset": offset, }, router_list_params.RouterListParams, @@ -480,6 +492,10 @@ async def create( Create a new router with the specified configuration. Args: + project_id: Project ID + + region_id: Region ID + name: name of router external_gateway_info: External gateway configuration. Use type 'default' to let the platform @@ -592,6 +608,7 @@ def list( project_id: int | None = None, region_id: int | None = None, limit: int | Omit = omit, + name: str | Omit = omit, offset: 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. @@ -604,9 +621,15 @@ def list( List all routers in the specified project and region. Args: - limit: Limit the number of returned routers + project_id: Project ID + + region_id: Region ID + + limit: Limit of items on a single page + + name: Optional. Filter routers by name - offset: Offset value is used to exclude the first set of records from the result + offset: Offset in results list extra_headers: Send extra headers @@ -631,6 +654,7 @@ def list( query=maybe_transform( { "limit": limit, + "name": name, "offset": offset, }, router_list_params.RouterListParams, diff --git a/src/gcore/types/cloud/gpu_baremetal/clusters/gpu_baremetal_cluster_server.py b/src/gcore/types/cloud/gpu_baremetal/clusters/gpu_baremetal_cluster_server.py index ff1e581f..9e58b475 100644 --- a/src/gcore/types/cloud/gpu_baremetal/clusters/gpu_baremetal_cluster_server.py +++ b/src/gcore/types/cloud/gpu_baremetal/clusters/gpu_baremetal_cluster_server.py @@ -28,6 +28,9 @@ class GPUBaremetalClusterServer(BaseModel): flavor: str """Unique flavor identifier""" + has_pending_changes: bool + """True if there are pending (not yet applied) server settings changes""" + image_id: Optional[str] = None """Server's image UUID""" diff --git a/src/gcore/types/cloud/gpu_baremetal/gpu_baremetal_cluster.py b/src/gcore/types/cloud/gpu_baremetal/gpu_baremetal_cluster.py index cba48130..ae01de52 100644 --- a/src/gcore/types/cloud/gpu_baremetal/gpu_baremetal_cluster.py +++ b/src/gcore/types/cloud/gpu_baremetal/gpu_baremetal_cluster.py @@ -131,6 +131,11 @@ class GPUBaremetalCluster(BaseModel): flavor: str """Cluster flavor name""" + has_pending_changes: bool + """ + True if any server in the cluster has pending (not yet applied) settings changes + """ + image_id: str """Image ID""" diff --git a/src/gcore/types/cloud/gpu_virtual/clusters/gpu_virtual_cluster_server.py b/src/gcore/types/cloud/gpu_virtual/clusters/gpu_virtual_cluster_server.py index d8126e53..81b25a59 100644 --- a/src/gcore/types/cloud/gpu_virtual/clusters/gpu_virtual_cluster_server.py +++ b/src/gcore/types/cloud/gpu_virtual/clusters/gpu_virtual_cluster_server.py @@ -28,6 +28,9 @@ class GPUVirtualClusterServer(BaseModel): flavor: str """Unique flavor identifier""" + has_pending_changes: bool + """True if there are pending (not yet applied) server settings changes""" + image_id: Optional[str] = None """Server's image UUID""" diff --git a/src/gcore/types/cloud/gpu_virtual/gpu_virtual_cluster.py b/src/gcore/types/cloud/gpu_virtual/gpu_virtual_cluster.py index d31d1b36..89333e0d 100644 --- a/src/gcore/types/cloud/gpu_virtual/gpu_virtual_cluster.py +++ b/src/gcore/types/cloud/gpu_virtual/gpu_virtual_cluster.py @@ -165,6 +165,11 @@ class GPUVirtualCluster(BaseModel): flavor: str """Cluster flavor name""" + has_pending_changes: bool + """ + True if any server in the cluster has pending (not yet applied) settings changes + """ + name: str """Cluster name""" diff --git a/src/gcore/types/cloud/networks/router_create_params.py b/src/gcore/types/cloud/networks/router_create_params.py index 89f0c379..6d1be0e5 100644 --- a/src/gcore/types/cloud/networks/router_create_params.py +++ b/src/gcore/types/cloud/networks/router_create_params.py @@ -17,8 +17,10 @@ class RouterCreateParams(TypedDict, total=False): project_id: int + """Project ID""" region_id: int + """Region ID""" name: Required[str] """name of router""" diff --git a/src/gcore/types/cloud/networks/router_list_params.py b/src/gcore/types/cloud/networks/router_list_params.py index 4842c311..b53732d4 100644 --- a/src/gcore/types/cloud/networks/router_list_params.py +++ b/src/gcore/types/cloud/networks/router_list_params.py @@ -9,11 +9,16 @@ class RouterListParams(TypedDict, total=False): project_id: int + """Project ID""" region_id: int + """Region ID""" limit: int - """Limit the number of returned routers""" + """Limit of items on a single page""" + + name: str + """Optional. Filter routers by name""" offset: int - """Offset value is used to exclude the first set of records from the result""" + """Offset in results list""" diff --git a/tests/api_resources/cloud/networks/test_routers.py b/tests/api_resources/cloud/networks/test_routers.py index 968607af..942646ca 100644 --- a/tests/api_resources/cloud/networks/test_routers.py +++ b/tests/api_resources/cloud/networks/test_routers.py @@ -26,8 +26,8 @@ class TestRouters: @parametrize def test_method_create(self, client: Gcore) -> None: router = client.cloud.networks.routers.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", ) assert_matches_type(TaskIDList, router, path=["response"]) @@ -35,8 +35,8 @@ def test_method_create(self, client: Gcore) -> None: @parametrize def test_method_create_with_all_params(self, client: Gcore) -> None: router = client.cloud.networks.routers.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", external_gateway_info={ "enable_snat": True, @@ -60,8 +60,8 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: @parametrize def test_raw_response_create(self, client: Gcore) -> None: response = client.cloud.networks.routers.with_raw_response.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", ) @@ -73,8 +73,8 @@ def test_raw_response_create(self, client: Gcore) -> None: @parametrize def test_streaming_response_create(self, client: Gcore) -> None: with client.cloud.networks.routers.with_streaming_response.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", ) as response: assert not response.is_closed @@ -162,17 +162,18 @@ def test_path_params_update(self, client: Gcore) -> None: @parametrize def test_method_list(self, client: Gcore) -> None: router = client.cloud.networks.routers.list( - project_id=0, - region_id=0, + project_id=1, + region_id=1, ) assert_matches_type(SyncOffsetPage[Router], router, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: router = client.cloud.networks.routers.list( - project_id=0, - region_id=0, - limit=0, + project_id=1, + region_id=1, + limit=10, + name="router-name", offset=0, ) assert_matches_type(SyncOffsetPage[Router], router, path=["response"]) @@ -180,8 +181,8 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: @parametrize def test_raw_response_list(self, client: Gcore) -> None: response = client.cloud.networks.routers.with_raw_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=1, ) assert response.is_closed is True @@ -192,8 +193,8 @@ def test_raw_response_list(self, client: Gcore) -> None: @parametrize def test_streaming_response_list(self, client: Gcore) -> None: with client.cloud.networks.routers.with_streaming_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=1, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -415,8 +416,8 @@ class TestAsyncRouters: @parametrize async def test_method_create(self, async_client: AsyncGcore) -> None: router = await async_client.cloud.networks.routers.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", ) assert_matches_type(TaskIDList, router, path=["response"]) @@ -424,8 +425,8 @@ async def test_method_create(self, async_client: AsyncGcore) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> None: router = await async_client.cloud.networks.routers.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", external_gateway_info={ "enable_snat": True, @@ -449,8 +450,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> @parametrize async def test_raw_response_create(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.networks.routers.with_raw_response.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", ) @@ -462,8 +463,8 @@ async def test_raw_response_create(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncGcore) -> None: async with async_client.cloud.networks.routers.with_streaming_response.create( - project_id=0, - region_id=0, + project_id=1, + region_id=1, name="my_wonderful_router", ) as response: assert not response.is_closed @@ -551,17 +552,18 @@ async def test_path_params_update(self, async_client: AsyncGcore) -> None: @parametrize async def test_method_list(self, async_client: AsyncGcore) -> None: router = await async_client.cloud.networks.routers.list( - project_id=0, - region_id=0, + project_id=1, + region_id=1, ) assert_matches_type(AsyncOffsetPage[Router], router, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: router = await async_client.cloud.networks.routers.list( - project_id=0, - region_id=0, - limit=0, + project_id=1, + region_id=1, + limit=10, + name="router-name", offset=0, ) assert_matches_type(AsyncOffsetPage[Router], router, path=["response"]) @@ -569,8 +571,8 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: response = await async_client.cloud.networks.routers.with_raw_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=1, ) assert response.is_closed is True @@ -581,8 +583,8 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: async with async_client.cloud.networks.routers.with_streaming_response.list( - project_id=0, - region_id=0, + project_id=1, + region_id=1, ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" From 0110236e7efb9269398475d2500e078c599037f6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:36:22 +0000 Subject: [PATCH 16/19] feat(api): aggregated API specs update --- .stats.yml | 4 ++-- src/gcore/types/waap/domains/waap_request_details.py | 2 +- src/gcore/types/waap/waap_request_summary.py | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 34273db4..c8153919 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-c0227d112d00ea1367115bffc78528aae50c3bef4b763b67f291c20da36e759c.yml -openapi_spec_hash: 728075186aa56e84ef78f6df633242e7 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-aaf167c2d2ddf471ded53fc4ee8622cf302ee235a09b683731bdabbf499fe40c.yml +openapi_spec_hash: 359e8df7782a96098561b772f10771cc config_hash: 1eeaf5413c25f32913ceebe18d37cc3e diff --git a/src/gcore/types/waap/domains/waap_request_details.py b/src/gcore/types/waap/domains/waap_request_details.py index 0eae8cfc..16475cd9 100644 --- a/src/gcore/types/waap/domains/waap_request_details.py +++ b/src/gcore/types/waap/domains/waap_request_details.py @@ -186,7 +186,7 @@ class WaapRequestDetails(BaseModel): """Name of the triggered rule""" scheme: str - """The HTTP scheme of the request that generated an event""" + """The URI scheme of the request that generated an event""" session_id: str """The session ID associated with the request.""" diff --git a/src/gcore/types/waap/waap_request_summary.py b/src/gcore/types/waap/waap_request_summary.py index 3e3926ab..22be6aba 100644 --- a/src/gcore/types/waap/waap_request_summary.py +++ b/src/gcore/types/waap/waap_request_summary.py @@ -70,5 +70,11 @@ class WaapRequestSummary(BaseModel): user_agent_client: str """Client from parsed User agent header""" + http_version: Optional[str] = None + """HTTP version of request""" + + scheme: Optional[str] = None + """The URI scheme of the request that generated an event""" + session_id: Optional[str] = None """The session ID associated with the request.""" From 284df4cedcfac4b86c746c963fc69b5af183d014 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 10:38:08 +0000 Subject: [PATCH 17/19] feat(api): aggregated API specs update --- .stats.yml | 4 ++-- src/gcore/resources/cloud/load_balancers/listeners.py | 8 ++++++++ .../types/cloud/load_balancers/listener_list_params.py | 3 +++ .../api_resources/cloud/load_balancers/test_listeners.py | 2 ++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c8153919..776c19ff 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-aaf167c2d2ddf471ded53fc4ee8622cf302ee235a09b683731bdabbf499fe40c.yml -openapi_spec_hash: 359e8df7782a96098561b772f10771cc +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-434ab5235704e2eb7332e76790a3457b5ab5f77b8e58dbac959f42c2d7b6cd17.yml +openapi_spec_hash: a89fe96d63c7671eef3c9c9fd6c46094 config_hash: 1eeaf5413c25f32913ceebe18d37cc3e diff --git a/src/gcore/resources/cloud/load_balancers/listeners.py b/src/gcore/resources/cloud/load_balancers/listeners.py index 5de5286e..be4aecd1 100644 --- a/src/gcore/resources/cloud/load_balancers/listeners.py +++ b/src/gcore/resources/cloud/load_balancers/listeners.py @@ -274,6 +274,7 @@ def list( region_id: int | None = None, limit: int | Omit = omit, load_balancer_id: str | Omit = omit, + name: str | Omit = omit, offset: int | Omit = omit, show_stats: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -295,6 +296,8 @@ def list( load_balancer_id: Load Balancer ID + name: Filter by name + offset: Optional. Offset value is used to exclude the first set of records from the result @@ -323,6 +326,7 @@ def list( { "limit": limit, "load_balancer_id": load_balancer_id, + "name": name, "offset": offset, "show_stats": show_stats, }, @@ -856,6 +860,7 @@ async def list( region_id: int | None = None, limit: int | Omit = omit, load_balancer_id: str | Omit = omit, + name: str | Omit = omit, offset: int | Omit = omit, show_stats: bool | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -877,6 +882,8 @@ async def list( load_balancer_id: Load Balancer ID + name: Filter by name + offset: Optional. Offset value is used to exclude the first set of records from the result @@ -905,6 +912,7 @@ async def list( { "limit": limit, "load_balancer_id": load_balancer_id, + "name": name, "offset": offset, "show_stats": show_stats, }, diff --git a/src/gcore/types/cloud/load_balancers/listener_list_params.py b/src/gcore/types/cloud/load_balancers/listener_list_params.py index d0eff492..cb277ce3 100644 --- a/src/gcore/types/cloud/load_balancers/listener_list_params.py +++ b/src/gcore/types/cloud/load_balancers/listener_list_params.py @@ -20,6 +20,9 @@ class ListenerListParams(TypedDict, total=False): load_balancer_id: str """Load Balancer ID""" + name: str + """Filter by name""" + offset: int """Optional. diff --git a/tests/api_resources/cloud/load_balancers/test_listeners.py b/tests/api_resources/cloud/load_balancers/test_listeners.py index 4f19fe59..212dc211 100644 --- a/tests/api_resources/cloud/load_balancers/test_listeners.py +++ b/tests/api_resources/cloud/load_balancers/test_listeners.py @@ -175,6 +175,7 @@ def test_method_list_with_all_params(self, client: Gcore) -> None: region_id=1, limit=1000, load_balancer_id="00000000-0000-4000-8000-000000000000", + name="listener-name", offset=0, show_stats=True, ) @@ -482,6 +483,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> No region_id=1, limit=1000, load_balancer_id="00000000-0000-4000-8000-000000000000", + name="listener-name", offset=0, show_stats=True, ) From 1bf3513b19eda2cd35230947dd36a1b09f66e419 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:14:44 +0000 Subject: [PATCH 18/19] chore(cloud)!: remove deprecated cloud.inference.deployments.get_api_key --- .stats.yml | 8 +- src/gcore/resources/cloud/api.md | 1 - .../inference/deployments/deployments.py | 112 ------------------ src/gcore/types/cloud/inference/__init__.py | 1 - .../inference/inference_deployment_api_key.py | 15 --- .../cloud/inference/test_deployments.py | 97 --------------- 6 files changed, 4 insertions(+), 230 deletions(-) delete mode 100644 src/gcore/types/cloud/inference/inference_deployment_api_key.py diff --git a/.stats.yml b/.stats.yml index 776c19ff..f3ae73c6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 658 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-434ab5235704e2eb7332e76790a3457b5ab5f77b8e58dbac959f42c2d7b6cd17.yml -openapi_spec_hash: a89fe96d63c7671eef3c9c9fd6c46094 -config_hash: 1eeaf5413c25f32913ceebe18d37cc3e +configured_endpoints: 657 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e8141952a77a5faabf98feca4f964baaef5b84aa66d785c8b35357e48928df9c.yml +openapi_spec_hash: a5ff6021de586dafea9df886214ceb2c +config_hash: 0b9fe3822aef92ebe43ad609b931bfc8 diff --git a/src/gcore/resources/cloud/api.md b/src/gcore/resources/cloud/api.md index 17de3c77..bfdb6c92 100644 --- a/src/gcore/resources/cloud/api.md +++ b/src/gcore/resources/cloud/api.md @@ -524,7 +524,6 @@ Methods: - client.cloud.inference.deployments.list(\*, project_id, \*\*params) -> SyncOffsetPage[InferenceDeployment] - client.cloud.inference.deployments.delete(deployment_name, \*, project_id) -> TaskIDList - client.cloud.inference.deployments.get(deployment_name, \*, project_id) -> InferenceDeployment -- client.cloud.inference.deployments.get_api_key(deployment_name, \*, project_id) -> InferenceDeploymentAPIKey - client.cloud.inference.deployments.start(deployment_name, \*, project_id) -> None - client.cloud.inference.deployments.stop(deployment_name, \*, project_id) -> None diff --git a/src/gcore/resources/cloud/inference/deployments/deployments.py b/src/gcore/resources/cloud/inference/deployments/deployments.py index cd0bafa7..a5bc5505 100644 --- a/src/gcore/resources/cloud/inference/deployments/deployments.py +++ b/src/gcore/resources/cloud/inference/deployments/deployments.py @@ -2,7 +2,6 @@ from __future__ import annotations -import typing_extensions from typing import Dict, Iterable, Optional import httpx @@ -30,7 +29,6 @@ from .....types.cloud.inference import deployment_list_params, deployment_create_params, deployment_update_params from .....types.cloud.task_id_list import TaskIDList from .....types.cloud.inference.inference_deployment import InferenceDeployment -from .....types.cloud.inference.inference_deployment_api_key import InferenceDeploymentAPIKey __all__ = ["DeploymentsResource", "AsyncDeploymentsResource"] @@ -439,51 +437,6 @@ def get( cast_to=InferenceDeployment, ) - @typing_extensions.deprecated("deprecated") - def get_api_key( - self, - deployment_name: str, - *, - project_id: int | None = None, - # 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, - ) -> InferenceDeploymentAPIKey: - """ - Get inference deployment API key - - Args: - project_id: Project ID - - deployment_name: Inference instance name. - - 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 not deployment_name: - raise ValueError(f"Expected a non-empty value for `deployment_name` but received {deployment_name!r}") - return self._get( - path_template( - "/cloud/v3/inference/{project_id}/deployments/{deployment_name}/apikey", - project_id=project_id, - deployment_name=deployment_name, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=InferenceDeploymentAPIKey, - ) - def start( self, deployment_name: str, @@ -1166,51 +1119,6 @@ async def get( cast_to=InferenceDeployment, ) - @typing_extensions.deprecated("deprecated") - async def get_api_key( - self, - deployment_name: str, - *, - project_id: int | None = None, - # 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, - ) -> InferenceDeploymentAPIKey: - """ - Get inference deployment API key - - Args: - project_id: Project ID - - deployment_name: Inference instance name. - - 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 not deployment_name: - raise ValueError(f"Expected a non-empty value for `deployment_name` but received {deployment_name!r}") - return await self._get( - path_template( - "/cloud/v3/inference/{project_id}/deployments/{deployment_name}/apikey", - project_id=project_id, - deployment_name=deployment_name, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=InferenceDeploymentAPIKey, - ) - async def start( self, deployment_name: str, @@ -1508,11 +1416,6 @@ def __init__(self, deployments: DeploymentsResource) -> None: self.get = to_raw_response_wrapper( deployments.get, ) - self.get_api_key = ( # pyright: ignore[reportDeprecated] - to_raw_response_wrapper( - deployments.get_api_key, # pyright: ignore[reportDeprecated], - ) - ) self.start = to_raw_response_wrapper( deployments.start, ) @@ -1553,11 +1456,6 @@ def __init__(self, deployments: AsyncDeploymentsResource) -> None: self.get = async_to_raw_response_wrapper( deployments.get, ) - self.get_api_key = ( # pyright: ignore[reportDeprecated] - async_to_raw_response_wrapper( - deployments.get_api_key, # pyright: ignore[reportDeprecated], - ) - ) self.start = async_to_raw_response_wrapper( deployments.start, ) @@ -1598,11 +1496,6 @@ def __init__(self, deployments: DeploymentsResource) -> None: self.get = to_streamed_response_wrapper( deployments.get, ) - self.get_api_key = ( # pyright: ignore[reportDeprecated] - to_streamed_response_wrapper( - deployments.get_api_key, # pyright: ignore[reportDeprecated], - ) - ) self.start = to_streamed_response_wrapper( deployments.start, ) @@ -1643,11 +1536,6 @@ def __init__(self, deployments: AsyncDeploymentsResource) -> None: self.get = async_to_streamed_response_wrapper( deployments.get, ) - self.get_api_key = ( # pyright: ignore[reportDeprecated] - async_to_streamed_response_wrapper( - deployments.get_api_key, # pyright: ignore[reportDeprecated], - ) - ) self.start = async_to_streamed_response_wrapper( deployments.start, ) diff --git a/src/gcore/types/cloud/inference/__init__.py b/src/gcore/types/cloud/inference/__init__.py index 66a4d198..194d24e1 100644 --- a/src/gcore/types/cloud/inference/__init__.py +++ b/src/gcore/types/cloud/inference/__init__.py @@ -22,7 +22,6 @@ from .deployment_create_params import DeploymentCreateParams as DeploymentCreateParams from .deployment_update_params import DeploymentUpdateParams as DeploymentUpdateParams from .inference_api_key_created import InferenceAPIKeyCreated as InferenceAPIKeyCreated -from .inference_deployment_api_key import InferenceDeploymentAPIKey as InferenceDeploymentAPIKey from .inference_registry_credentials import InferenceRegistryCredentials as InferenceRegistryCredentials from .registry_credential_list_params import RegistryCredentialListParams as RegistryCredentialListParams from .registry_credential_create_params import RegistryCredentialCreateParams as RegistryCredentialCreateParams diff --git a/src/gcore/types/cloud/inference/inference_deployment_api_key.py b/src/gcore/types/cloud/inference/inference_deployment_api_key.py deleted file mode 100644 index e6df4ffa..00000000 --- a/src/gcore/types/cloud/inference/inference_deployment_api_key.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal - -from ...._models import BaseModel - -__all__ = ["InferenceDeploymentAPIKey"] - - -class InferenceDeploymentAPIKey(BaseModel): - secret: str - """API key secret""" - - status: Literal["PENDING", "READY"] - """API key status""" diff --git a/tests/api_resources/cloud/inference/test_deployments.py b/tests/api_resources/cloud/inference/test_deployments.py index a9e40264..481841b5 100644 --- a/tests/api_resources/cloud/inference/test_deployments.py +++ b/tests/api_resources/cloud/inference/test_deployments.py @@ -13,11 +13,8 @@ from gcore.types.cloud import TaskIDList from gcore.types.cloud.inference import ( InferenceDeployment, - InferenceDeploymentAPIKey, ) -# pyright: reportDeprecated=false - base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -496,53 +493,6 @@ def test_path_params_get(self, client: Gcore) -> None: project_id=1, ) - @parametrize - def test_method_get_api_key(self, client: Gcore) -> None: - with pytest.warns(DeprecationWarning): - deployment = client.cloud.inference.deployments.get_api_key( - deployment_name="my-instance", - project_id=1, - ) - - assert_matches_type(InferenceDeploymentAPIKey, deployment, path=["response"]) - - @parametrize - def test_raw_response_get_api_key(self, client: Gcore) -> None: - with pytest.warns(DeprecationWarning): - response = client.cloud.inference.deployments.with_raw_response.get_api_key( - deployment_name="my-instance", - project_id=1, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - deployment = response.parse() - assert_matches_type(InferenceDeploymentAPIKey, deployment, path=["response"]) - - @parametrize - def test_streaming_response_get_api_key(self, client: Gcore) -> None: - with pytest.warns(DeprecationWarning): - with client.cloud.inference.deployments.with_streaming_response.get_api_key( - deployment_name="my-instance", - project_id=1, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - deployment = response.parse() - assert_matches_type(InferenceDeploymentAPIKey, deployment, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_get_api_key(self, client: Gcore) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `deployment_name` but received ''"): - client.cloud.inference.deployments.with_raw_response.get_api_key( - deployment_name="", - project_id=1, - ) - @parametrize def test_method_start(self, client: Gcore) -> None: deployment = client.cloud.inference.deployments.start( @@ -1105,53 +1055,6 @@ async def test_path_params_get(self, async_client: AsyncGcore) -> None: project_id=1, ) - @parametrize - async def test_method_get_api_key(self, async_client: AsyncGcore) -> None: - with pytest.warns(DeprecationWarning): - deployment = await async_client.cloud.inference.deployments.get_api_key( - deployment_name="my-instance", - project_id=1, - ) - - assert_matches_type(InferenceDeploymentAPIKey, deployment, path=["response"]) - - @parametrize - async def test_raw_response_get_api_key(self, async_client: AsyncGcore) -> None: - with pytest.warns(DeprecationWarning): - response = await async_client.cloud.inference.deployments.with_raw_response.get_api_key( - deployment_name="my-instance", - project_id=1, - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - deployment = await response.parse() - assert_matches_type(InferenceDeploymentAPIKey, deployment, path=["response"]) - - @parametrize - async def test_streaming_response_get_api_key(self, async_client: AsyncGcore) -> None: - with pytest.warns(DeprecationWarning): - async with async_client.cloud.inference.deployments.with_streaming_response.get_api_key( - deployment_name="my-instance", - project_id=1, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - deployment = await response.parse() - assert_matches_type(InferenceDeploymentAPIKey, deployment, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_get_api_key(self, async_client: AsyncGcore) -> None: - with pytest.warns(DeprecationWarning): - with pytest.raises(ValueError, match=r"Expected a non-empty value for `deployment_name` but received ''"): - await async_client.cloud.inference.deployments.with_raw_response.get_api_key( - deployment_name="", - project_id=1, - ) - @parametrize async def test_method_start(self, async_client: AsyncGcore) -> None: deployment = await async_client.cloud.inference.deployments.start( From 64436a5c2fdd7c5096fd8b44c770ee19e05e3799 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:44:49 +0000 Subject: [PATCH 19/19] release: 0.42.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/gcore/_version.py | 2 +- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ea2682c3..52afe059 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.41.0" + ".": "0.42.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cc98f8e3..410c7460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,50 @@ # Changelog +## 0.42.0 (2026-04-17) + +Full Changelog: [v0.41.0...v0.42.0](https://github.com/G-Core/gcore-python/compare/v0.41.0...v0.42.0) + +### ⚠ BREAKING CHANGES + +* **cloud:** remove deprecated cloud.inference.deployments.get_api_key + +### Features + +* **api:** aggregated API specs update ([284df4c](https://github.com/G-Core/gcore-python/commit/284df4cedcfac4b86c746c963fc69b5af183d014)) +* **api:** aggregated API specs update ([0110236](https://github.com/G-Core/gcore-python/commit/0110236e7efb9269398475d2500e078c599037f6)) +* **api:** aggregated API specs update ([51ad9c9](https://github.com/G-Core/gcore-python/commit/51ad9c97608b81a0358c3c8e06b3321cb331a06b)) +* **api:** aggregated API specs update ([eda0539](https://github.com/G-Core/gcore-python/commit/eda0539c7bac06e3451703469aabf50fb5290467)) +* **api:** aggregated API specs update ([01be2f5](https://github.com/G-Core/gcore-python/commit/01be2f5ba1c94daeff5e61a4672b9ff8b0bac7cc)) +* **api:** aggregated API specs update ([7beaf49](https://github.com/G-Core/gcore-python/commit/7beaf49ae0cd289d6a60a79de18ed722a9cbf088)) +* **api:** aggregated API specs update ([2f8fb0a](https://github.com/G-Core/gcore-python/commit/2f8fb0a0a1493b19a84a8d4fcb863ddfc2f66db8)) +* **api:** fix(cdn): harmonize pagination across CDN list endpoints ([c95a0b8](https://github.com/G-Core/gcore-python/commit/c95a0b8b047bb8f7370934fe35576648163e3a3e)) +* **cloud:** add delete_and_poll and examples for GPU baremetal clusters ([adc8f40](https://github.com/G-Core/gcore-python/commit/adc8f408f925862f13e19ac30c96684f40395aca)) +* **cloud:** add polling methods and examples for GPU virtual clusters ([1ed9b85](https://github.com/G-Core/gcore-python/commit/1ed9b85b772bd52971503e21a7e484af6812f5b4)) + + +### Bug Fixes + +* **client:** preserve hardcoded query params when merging with user params ([0971eaa](https://github.com/G-Core/gcore-python/commit/0971eaa023789ebaf5baeae39f4977cba3cbab1d)) +* **cloud:** align polling methods with underlying base method ([4b99901](https://github.com/G-Core/gcore-python/commit/4b999011b48a859a6f95649246ac3a5ba67ff62e)) +* **dns:** update network-mappings get_by_name to new endpoint path ([de11047](https://github.com/G-Core/gcore-python/commit/de110478944f7c9dc61ac3481dba876912e05ff6)) +* ensure file data are only sent as 1 parameter ([16f4262](https://github.com/G-Core/gcore-python/commit/16f4262f6ca89edcefe0315c13963e2d84f945c2)) +* **examples:** update baremetal image examples to use BaremetalImage type ([7e924f8](https://github.com/G-Core/gcore-python/commit/7e924f830c234be00e8c6cb940fe59ff4629a626)) + + +### Chores + +* **cloud:** remove deprecated cloud.inference.deployments.get_api_key ([1bf3513](https://github.com/G-Core/gcore-python/commit/1bf3513b19eda2cd35230947dd36a1b09f66e419)) + + +### Documentation + +* update examples ([fa05d93](https://github.com/G-Core/gcore-python/commit/fa05d9370764f706015b7628abd3ba0f12f880d7)) + + +### Refactors + +* **cloud:** split instance and baremetal image models ([fce124a](https://github.com/G-Core/gcore-python/commit/fce124a98b3eb6cec5bbcb46e60e424b4c6da298)) + ## 0.41.0 (2026-04-03) Full Changelog: [v0.40.0...v0.41.0](https://github.com/G-Core/gcore-python/compare/v0.40.0...v0.41.0) diff --git a/pyproject.toml b/pyproject.toml index 5f2b6b35..5002951b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gcore" -version = "0.41.0" +version = "0.42.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 139c4ad2..b88cfcf0 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.41.0" # x-release-please-version +__version__ = "0.42.0" # x-release-please-version