diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f7014c35..a7130553 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.0" + ".": "0.12.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 6209bbc0..bf75ef97 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 523 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-69c2f202e608b625930fb755ef34ae2fa30acce4fa987889e10402324aaf689e.yml -openapi_spec_hash: f6aa7b95639f6eb639e408ad321f2861 -config_hash: 53f1995f46a0e2f7e747e65bafa3d6e0 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-fa5e3eb5c4c01c6be5a8d036640779fa59a1931c596245748ed28601213caa3b.yml +openapi_spec_hash: 2ad55753d983df577077b3ce156ac4ca +config_hash: 94a708396796faa210065e6aa3afebc2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0a158d..3fcdfcb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.12.0 (2025-09-11) + +Full Changelog: [v0.11.0...v0.12.0](https://github.com/G-Core/gcore-python/compare/v0.11.0...v0.12.0) + +### Features + +* **api:** aggregated API specs update ([3fbc8f1](https://github.com/G-Core/gcore-python/commit/3fbc8f1a6126f4cb6d9e6a0aff7f35f67f77ec6f)) +* **cloud:** add create_and_poll() and delete_and_poll() to reserved fixed ips ([6e6efc0](https://github.com/G-Core/gcore-python/commit/6e6efc03de62b895199a043e0cf1b396aefb0d01)) +* **cloud:** add polling methods to volumes ([3537e8c](https://github.com/G-Core/gcore-python/commit/3537e8c5b6ebd0500868dfc2b14bfeba735fe1f5)) + + +### Refactors + +* **storage:** use v2 endpoint ([4a00499](https://github.com/G-Core/gcore-python/commit/4a00499c3bc80a6f06e476bd1c0d544bd1c46cef)) + ## 0.11.0 (2025-09-09) Full Changelog: [v0.10.0...v0.11.0](https://github.com/G-Core/gcore-python/compare/v0.10.0...v0.11.0) diff --git a/api.md b/api.md index 57501eeb..7a3fa350 100644 --- a/api.md +++ b/api.md @@ -1987,7 +1987,7 @@ from gcore.types.storage import Storage Methods: - client.storage.create(\*\*params) -> Storage -- client.storage.update(storage_id, \*\*params) -> Storage +- client.storage.update(storage_id, \*\*params) -> Storage - client.storage.list(\*\*params) -> SyncOffsetPage[Storage] - client.storage.delete(storage_id) -> None - client.storage.get(storage_id) -> Storage diff --git a/examples/cloud/reserved_fixed_ips.py b/examples/cloud/reserved_fixed_ips.py index 1233b661..313c6069 100644 --- a/examples/cloud/reserved_fixed_ips.py +++ b/examples/cloud/reserved_fixed_ips.py @@ -34,15 +34,11 @@ def main() -> None: def create_reserved_fixed_ip(*, client: Gcore) -> ReservedFixedIP: print("\n=== CREATE RESERVED FIXED IP ===") - task_list = client.cloud.reserved_fixed_ips.create( + port = client.cloud.reserved_fixed_ips.create_and_poll( type="external", ip_family="ipv4", is_vip=False, ) - task = client.cloud.tasks.poll(task_list.tasks[0]) - if not task.created_resources or not task.created_resources.ports or len(task.created_resources.ports) != 1: - raise RuntimeError("Expected exactly one port created in the task result") - port = client.cloud.reserved_fixed_ips.get(task.created_resources.ports[0]) print(f"Created reserved fixed IP: ID={port.port_id}, name={port.name}, IP={port.fixed_ip_address}") print("========================") return port @@ -93,8 +89,7 @@ def list_connected_ports(*, client: Gcore, port_id: str) -> None: def delete_reserved_fixed_ip(*, client: Gcore, port_id: str) -> None: print("\n=== DELETE RESERVED FIXED IP ===") - task_list = client.cloud.reserved_fixed_ips.delete(port_id) - client.cloud.tasks.poll(task_list.tasks[0]) + client.cloud.reserved_fixed_ips.delete_and_poll(port_id) print(f"Deleted reserved fixed IP: ID={port_id}") print("========================") diff --git a/examples/cloud/reserved_fixed_ips_async.py b/examples/cloud/reserved_fixed_ips_async.py index 3542a229..2a20d763 100644 --- a/examples/cloud/reserved_fixed_ips_async.py +++ b/examples/cloud/reserved_fixed_ips_async.py @@ -36,15 +36,11 @@ async def main() -> None: async def create_reserved_fixed_ip(*, client: AsyncGcore) -> ReservedFixedIP: print("\n=== CREATE RESERVED FIXED IP ===") - task_list = await client.cloud.reserved_fixed_ips.create( + port = await client.cloud.reserved_fixed_ips.create_and_poll( type="external", ip_family="ipv4", is_vip=False, ) - task = await client.cloud.tasks.poll(task_list.tasks[0]) - if not task.created_resources or not task.created_resources.ports or len(task.created_resources.ports) != 1: - raise RuntimeError("Expected exactly one port created in the task result") - port = await client.cloud.reserved_fixed_ips.get(task.created_resources.ports[0]) print(f"Created reserved fixed IP: ID={port.port_id}, name={port.name}, IP={port.fixed_ip_address}") print("========================") return port @@ -97,8 +93,7 @@ async def list_connected_ports(*, client: AsyncGcore, port_id: str) -> None: async def delete_reserved_fixed_ip(*, client: AsyncGcore, port_id: str) -> None: print("\n=== DELETE RESERVED FIXED IP ===") - task_list = await client.cloud.reserved_fixed_ips.delete(port_id) - await client.cloud.tasks.poll(task_list.tasks[0]) + await client.cloud.reserved_fixed_ips.delete_and_poll(port_id) print(f"Deleted reserved fixed IP: ID={port_id}") print("========================") diff --git a/examples/cloud/volumes.py b/examples/cloud/volumes.py index b23f0523..c27b41b2 100644 --- a/examples/cloud/volumes.py +++ b/examples/cloud/volumes.py @@ -34,18 +34,16 @@ def main() -> None: def create_volume(*, client: Gcore) -> str: print("\n=== CREATE VOLUME ===") - response = client.cloud.volumes.create( + volume = client.cloud.volumes.create_and_poll( name="gcore-go-example", size=1, source="new-volume", ) - task = client.cloud.tasks.poll(task_id=response.tasks[0]) - if task.created_resources is None or task.created_resources.volumes is None: - raise RuntimeError("Task completed but created_resources or volumes is missing") - volume_id: str = task.created_resources.volumes[0] - print(f"Created volume: ID={volume_id}") + if not volume.id: + raise RuntimeError("Failed to create volume") + print(f"Created volume: ID={volume.id}") print("========================") - return volume_id + return volume.id def list_volumes(*, client: Gcore) -> None: @@ -75,18 +73,14 @@ def update_volume(*, client: Gcore, volume_id: str) -> None: def attach_to_instance(*, client: Gcore, volume_id: str, instance_id: str) -> None: print("\n=== ATTACH TO INSTANCE ===") - response = client.cloud.volumes.attach_to_instance(volume_id=volume_id, instance_id=instance_id) - task_id = response.tasks[0] - client.cloud.tasks.poll(task_id=task_id) + client.cloud.volumes.attach_to_instance_and_poll(volume_id=volume_id, instance_id=instance_id) print(f"Attached volume to instance: volume_id={volume_id}, instance_id={instance_id}") print("========================") def detach_from_instance(*, client: Gcore, volume_id: str, instance_id: str) -> None: print("\n=== DETACH FROM INSTANCE ===") - response = client.cloud.volumes.detach_from_instance(volume_id=volume_id, instance_id=instance_id) - task_id = response.tasks[0] - client.cloud.tasks.poll(task_id=task_id) + client.cloud.volumes.detach_from_instance_and_poll(volume_id=volume_id, instance_id=instance_id) print(f"Detached volume from instance: volume_id={volume_id}, instance_id={instance_id}") print("========================") @@ -100,18 +94,14 @@ def change_type(*, client: Gcore, volume_id: str) -> None: def resize(*, client: Gcore, volume_id: str) -> None: print("\n=== RESIZE ===") - response = client.cloud.volumes.resize(volume_id=volume_id, size=2) - task_id = response.tasks[0] - client.cloud.tasks.poll(task_id=task_id) - print(f"Resized volume: ID={volume_id}, size=2 GiB") + volume = client.cloud.volumes.resize_and_poll(volume_id=volume_id, size=2) + print(f"Resized volume: ID={volume.id}, size={volume.size} GiB") print("========================") def delete_volume(*, client: Gcore, volume_id: str) -> None: print("\n=== DELETE VOLUME ===") - response = client.cloud.volumes.delete(volume_id=volume_id) - task_id = response.tasks[0] - client.cloud.tasks.poll(task_id=task_id) + client.cloud.volumes.delete_and_poll(volume_id=volume_id) print(f"Deleted volume: ID={volume_id}") print("========================") diff --git a/examples/cloud/volumes_async.py b/examples/cloud/volumes_async.py index 522e0ff9..7b053101 100644 --- a/examples/cloud/volumes_async.py +++ b/examples/cloud/volumes_async.py @@ -35,18 +35,16 @@ async def main() -> None: async def create_volume(*, client: AsyncGcore) -> str: print("\n=== CREATE VOLUME ===") - response = await client.cloud.volumes.create( + volume = await client.cloud.volumes.create_and_poll( name="gcore-go-example", size=1, source="new-volume", ) - task = await client.cloud.tasks.poll(task_id=response.tasks[0]) - if task.created_resources is None or task.created_resources.volumes is None: - raise RuntimeError("Task completed but created_resources or volumes is missing") - volume_id: str = task.created_resources.volumes[0] - print(f"Created volume: ID={volume_id}") + if not volume.id: + raise RuntimeError("Failed to create volume") + print(f"Created volume: ID={volume.id}") print("========================") - return volume_id + return volume.id async def list_volumes(*, client: AsyncGcore) -> None: @@ -77,18 +75,14 @@ async def update_volume(*, client: AsyncGcore, volume_id: str) -> None: async def attach_to_instance(*, client: AsyncGcore, volume_id: str, instance_id: str) -> None: print("\n=== ATTACH TO INSTANCE ===") - response = await client.cloud.volumes.attach_to_instance(volume_id=volume_id, instance_id=instance_id) - task_id = response.tasks[0] - await client.cloud.tasks.poll(task_id=task_id) + await client.cloud.volumes.attach_to_instance_and_poll(volume_id=volume_id, instance_id=instance_id) print(f"Attached volume to instance: volume_id={volume_id}, instance_id={instance_id}") print("========================") async def detach_from_instance(*, client: AsyncGcore, volume_id: str, instance_id: str) -> None: print("\n=== DETACH FROM INSTANCE ===") - response = await client.cloud.volumes.detach_from_instance(volume_id=volume_id, instance_id=instance_id) - task_id = response.tasks[0] - await client.cloud.tasks.poll(task_id=task_id) + await client.cloud.volumes.detach_from_instance_and_poll(volume_id=volume_id, instance_id=instance_id) print(f"Detached volume from instance: volume_id={volume_id}, instance_id={instance_id}") print("========================") @@ -102,18 +96,14 @@ async def change_type(*, client: AsyncGcore, volume_id: str) -> None: async def resize(*, client: AsyncGcore, volume_id: str) -> None: print("\n=== RESIZE ===") - response = await client.cloud.volumes.resize(volume_id=volume_id, size=2) - task_id = response.tasks[0] - await client.cloud.tasks.poll(task_id=task_id) - print(f"Resized volume: ID={volume_id}, size=2 GiB") + volume = await client.cloud.volumes.resize_and_poll(volume_id=volume_id, size=2) + print(f"Resized volume: ID={volume.id}, size={volume.size} GiB") print("========================") async def delete_volume(*, client: AsyncGcore, volume_id: str) -> None: print("\n=== DELETE VOLUME ===") - response = await client.cloud.volumes.delete(volume_id=volume_id) - task_id = response.tasks[0] - await client.cloud.tasks.poll(task_id=task_id) + await client.cloud.volumes.delete_and_poll(volume_id=volume_id) print(f"Deleted volume: ID={volume_id}") print("========================") diff --git a/pyproject.toml b/pyproject.toml index 78c99b8f..29e96268 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gcore" -version = "0.11.0" +version = "0.12.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 1ef4a3a8..7f12d5d7 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.11.0" # x-release-please-version +__version__ = "0.12.0" # x-release-please-version diff --git a/src/gcore/resources/cloud/reserved_fixed_ips/reserved_fixed_ips.py b/src/gcore/resources/cloud/reserved_fixed_ips/reserved_fixed_ips.py index d5b0e030..5acd6b62 100644 --- a/src/gcore/resources/cloud/reserved_fixed_ips/reserved_fixed_ips.py +++ b/src/gcore/resources/cloud/reserved_fixed_ips/reserved_fixed_ips.py @@ -455,6 +455,298 @@ def get( cast_to=ReservedFixedIP, ) + @overload + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + type: Literal["external"], + ip_family: Optional[InterfaceIPFamily] | NotGiven = NOT_GIVEN, + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + type: Must be 'external' + + ip_family: Which subnets should be selected: IPv4, IPv6 or use dual stack. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + subnet_id: str, + type: Literal["subnet"], + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + subnet_id: Reserved fixed IP will be allocated in this subnet + + type: Must be 'subnet'. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + network_id: str, + type: Literal["any_subnet"], + ip_family: Optional[InterfaceIPFamily] | NotGiven = NOT_GIVEN, + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + network_id: Reserved fixed IP will be allocated in a subnet of this network + + type: Must be '`any_subnet`'. + + ip_family: Which subnets should be selected: IPv4, IPv6 or use dual stack. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + ip_address: str, + network_id: str, + type: Literal["ip_address"], + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + ip_address: Reserved fixed IP will be allocated the given IP address + + network_id: Reserved fixed IP will be allocated in a subnet of this network + + type: Must be '`ip_address`'. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + port_id: str, + type: Literal["port"], + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + port_id: Port ID to make a reserved fixed IP (for example, `vip_port_id` of the Load + Balancer entity). + + type: Must be 'port'. + + 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 + """ + ... + + @required_args( + ["type"], + ["subnet_id", "type"], + ["network_id", "type"], + ["ip_address", "network_id", "type"], + ["port_id", "type"], + ) + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + type: Literal["external"] | Literal["subnet"] | Literal["any_subnet"] | Literal["ip_address"] | Literal["port"], + ip_family: Optional[InterfaceIPFamily] | NotGiven = NOT_GIVEN, + is_vip: bool | NotGiven = NOT_GIVEN, + subnet_id: str | NotGiven = NOT_GIVEN, + network_id: str | NotGiven = NOT_GIVEN, + ip_address: str | NotGiven = NOT_GIVEN, + port_id: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration 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: TaskIDList = self.create( # type: ignore + project_id=project_id, + region_id=region_id, + type=type, + ip_family=ip_family, + is_vip=is_vip, + subnet_id=subnet_id, + network_id=network_id, + ip_address=ip_address, + port_id=port_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: # type: ignore + raise ValueError("Expected at least one task to be created") + task = self._client.cloud.tasks.poll( + task_id=response.tasks[0], # type: ignore + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + if ( + task.created_resources is None + or task.created_resources.ports is None + or len(task.created_resources.ports) != 1 + ): + raise ValueError("Task completed but created_resources or ports is missing or invalid") + created_port_id = task.created_resources.ports[0] + return self.get( + port_id=created_port_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, + port_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> None: + """ + Delete a specific reserved fixed IP and all its associated resources 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( + port_id=port_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + class AsyncReservedFixedIPsResource(AsyncAPIResource): @cached_property @@ -876,6 +1168,298 @@ async def get( cast_to=ReservedFixedIP, ) + @overload + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + type: Literal["external"], + ip_family: Optional[InterfaceIPFamily] | NotGiven = NOT_GIVEN, + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + type: Must be 'external' + + ip_family: Which subnets should be selected: IPv4, IPv6 or use dual stack. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + subnet_id: str, + type: Literal["subnet"], + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + subnet_id: Reserved fixed IP will be allocated in this subnet + + type: Must be 'subnet'. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + network_id: str, + type: Literal["any_subnet"], + ip_family: Optional[InterfaceIPFamily] | NotGiven = NOT_GIVEN, + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + network_id: Reserved fixed IP will be allocated in a subnet of this network + + type: Must be '`any_subnet`'. + + ip_family: Which subnets should be selected: IPv4, IPv6 or use dual stack. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + ip_address: str, + network_id: str, + type: Literal["ip_address"], + is_vip: bool | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + ip_address: Reserved fixed IP will be allocated the given IP address + + network_id: Reserved fixed IP will be allocated in a subnet of this network + + type: Must be '`ip_address`'. + + is_vip: If reserved fixed IP is a VIP + + 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 + """ + ... + + @overload + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + port_id: str, + type: Literal["port"], + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + Args: + port_id: Port ID to make a reserved fixed IP (for example, `vip_port_id` of the Load + Balancer entity). + + type: Must be 'port'. + + 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 + """ + ... + + @required_args( + ["type"], + ["subnet_id", "type"], + ["network_id", "type"], + ["ip_address", "network_id", "type"], + ["port_id", "type"], + ) + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + type: Literal["external"] | Literal["subnet"] | Literal["any_subnet"] | Literal["ip_address"] | Literal["port"], + ip_family: Optional[InterfaceIPFamily] | NotGiven = NOT_GIVEN, + is_vip: bool | NotGiven = NOT_GIVEN, + subnet_id: str | NotGiven = NOT_GIVEN, + network_id: str | NotGiven = NOT_GIVEN, + ip_address: str | NotGiven = NOT_GIVEN, + port_id: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> ReservedFixedIP: + """ + Create a new reserved fixed IP with the specified configuration 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: TaskIDList = await self.create( # type: ignore + project_id=project_id, + region_id=region_id, + type=type, + ip_family=ip_family, + is_vip=is_vip, + subnet_id=subnet_id, + network_id=network_id, + ip_address=ip_address, + port_id=port_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: # type: ignore + raise ValueError("Expected at least one task to be created") + task = await self._client.cloud.tasks.poll( + task_id=response.tasks[0], # type: ignore + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + if ( + task.created_resources is None + or task.created_resources.ports is None + or len(task.created_resources.ports) != 1 + ): + raise ValueError("Task completed but created_resources or ports is missing or invalid") + created_port_id = task.created_resources.ports[0] + return await self.get( + port_id=created_port_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, + port_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> None: + """ + Delete a specific reserved fixed IP and all its associated resources 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( + port_id=port_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + class ReservedFixedIPsResourceWithRawResponse: def __init__(self, reserved_fixed_ips: ReservedFixedIPsResource) -> None: @@ -893,6 +1477,12 @@ def __init__(self, reserved_fixed_ips: ReservedFixedIPsResource) -> None: self.get = to_raw_response_wrapper( reserved_fixed_ips.get, ) + self.create_and_poll = to_raw_response_wrapper( + reserved_fixed_ips.create_and_poll, + ) + self.delete_and_poll = to_raw_response_wrapper( + reserved_fixed_ips.delete_and_poll, + ) @cached_property def vip(self) -> VipResourceWithRawResponse: @@ -915,6 +1505,12 @@ def __init__(self, reserved_fixed_ips: AsyncReservedFixedIPsResource) -> None: self.get = async_to_raw_response_wrapper( reserved_fixed_ips.get, ) + self.create_and_poll = async_to_raw_response_wrapper( + reserved_fixed_ips.create_and_poll, + ) + self.delete_and_poll = async_to_raw_response_wrapper( + reserved_fixed_ips.delete_and_poll, + ) @cached_property def vip(self) -> AsyncVipResourceWithRawResponse: @@ -937,6 +1533,12 @@ def __init__(self, reserved_fixed_ips: ReservedFixedIPsResource) -> None: self.get = to_streamed_response_wrapper( reserved_fixed_ips.get, ) + self.create_and_poll = to_streamed_response_wrapper( + reserved_fixed_ips.create_and_poll, + ) + self.delete_and_poll = to_streamed_response_wrapper( + reserved_fixed_ips.delete_and_poll, + ) @cached_property def vip(self) -> VipResourceWithStreamingResponse: @@ -959,6 +1561,12 @@ def __init__(self, reserved_fixed_ips: AsyncReservedFixedIPsResource) -> None: self.get = async_to_streamed_response_wrapper( reserved_fixed_ips.get, ) + self.create_and_poll = async_to_streamed_response_wrapper( + reserved_fixed_ips.create_and_poll, + ) + self.delete_and_poll = async_to_streamed_response_wrapper( + reserved_fixed_ips.delete_and_poll, + ) @cached_property def vip(self) -> AsyncVipResourceWithStreamingResponse: diff --git a/src/gcore/resources/cloud/volumes.py b/src/gcore/resources/cloud/volumes.py index ed0babaa..60784300 100644 --- a/src/gcore/resources/cloud/volumes.py +++ b/src/gcore/resources/cloud/volumes.py @@ -844,29 +844,8 @@ def revert_to_last_snapshot( cast_to=NoneType, ) - -class AsyncVolumesResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncVolumesResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/G-Core/gcore-python#accessing-raw-response-data-eg-headers - """ - return AsyncVolumesResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncVolumesResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/G-Core/gcore-python#with_streaming_response - """ - return AsyncVolumesResourceWithStreamingResponse(self) - @overload - async def create( + def create_and_poll( self, *, project_id: int | None = None, @@ -881,14 +860,15 @@ async def create( tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TaskIDList: - """Create a new volume in the project and region. + ) -> Volume: + """Create a new volume in the project and region and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. The volume can be created from scratch, from an image, or from a snapshot. Optionally attach the volume to an @@ -935,7 +915,7 @@ async def create( ... @overload - async def create( + def create_and_poll( self, *, project_id: int | None = None, @@ -950,14 +930,15 @@ async def create( tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TaskIDList: - """Create a new volume in the project and region. + ) -> Volume: + """Create a new volume in the project and region and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. The volume can be created from scratch, from an image, or from a snapshot. Optionally attach the volume to an @@ -1005,7 +986,7 @@ async def create( ... @overload - async def create( + def create_and_poll( self, *, project_id: int | None = None, @@ -1019,14 +1000,15 @@ async def create( tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TaskIDList: - """Create a new volume in the project and region. + ) -> Volume: + """Create a new volume in the project and region and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. The volume can be created from scratch, from an image, or from a snapshot. Optionally attach the volume to an @@ -1073,7 +1055,7 @@ async def create( @required_args( ["image_id", "name", "size", "source"], ["name", "snapshot_id", "source"], ["name", "size", "source"] ) - async def create( + def create_and_poll( self, *, project_id: int | None = None, @@ -1089,170 +1071,609 @@ async def create( type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] | NotGiven = NOT_GIVEN, snapshot_id: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TaskIDList: - 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() - return await self._post( - f"/cloud/v1/volumes/{project_id}/{region_id}", - body=await async_maybe_transform( - { - "image_id": image_id, - "name": name, - "size": size, - "source": source, - "attachment_tag": attachment_tag, - "instance_id_to_attach_to": instance_id_to_attach_to, - "lifecycle_policy_ids": lifecycle_policy_ids, - "tags": tags, - "type_name": type_name, - "snapshot_id": snapshot_id, - }, - volume_create_params.VolumeCreateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=TaskIDList, + ) -> Volume: + """Create a new volume in the project and region 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: TaskIDList = self.create( # type: ignore + project_id=project_id, + region_id=region_id, + image_id=image_id, + name=name, + size=size, + source=source, + attachment_tag=attachment_tag, + instance_id_to_attach_to=instance_id_to_attach_to, + lifecycle_policy_ids=lifecycle_policy_ids, + tags=tags, + type_name=type_name, + snapshot_id=snapshot_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: # type: ignore + raise ValueError("Expected at least one task to be created") + task = self._client.cloud.tasks.poll( + task_id=response.tasks[0], # type: ignore + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + if task.created_resources is None or task.created_resources.volumes is None or len(task.created_resources.volumes) != 1: + raise ValueError("Task completed but created_resources or volumes is missing or invalid") + created_volume_id = task.created_resources.volumes[0] + return self.get( + volume_id=created_volume_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 update( + def delete_and_poll( self, volume_id: str, *, project_id: int | None = None, region_id: int | None = None, - name: str | NotGiven = NOT_GIVEN, - tags: Optional[TagUpdateMapParam] | NotGiven = NOT_GIVEN, + snapshots: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # 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, - ) -> Volume: - """ - Rename a volume or update tags - - Args: - project_id: Project ID - - region_id: Region ID + ) -> None: + """Delete a volume and all its snapshots 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( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + snapshots=snapshots, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) - volume_id: Volume ID + def attach_to_instance_and_poll( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + instance_id: str, + attachment_tag: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> None: + """Attach the volume to instance 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.attach_to_instance( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + instance_id=instance_id, + attachment_tag=attachment_tag, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) - name: Name + def detach_from_instance_and_poll( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + instance_id: str, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> None: + """Detach the volume from instance 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.detach_from_instance( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + instance_id=instance_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) - tags: Update key-value tags using JSON Merge Patch semantics (RFC 7386). Provide - key-value pairs to add or update tags. Set tag values to `null` to remove tags. - Unspecified tags remain unchanged. Read-only tags are always preserved and - cannot be modified. **Examples:** + def resize_and_poll( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + size: int, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> Volume: + """Increase the size of a volume 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.resize( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + size=size, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + return self.get( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) - - **Add/update tags:** - `{'tags': {'environment': 'production', 'team': 'backend'}}` adds new tags or - updates existing ones. - - **Delete tags:** `{'tags': {'`old_tag`': null}}` removes specific tags. - - **Remove all tags:** `{'tags': null}` removes all user-managed tags (read-only - tags are preserved). - - **Partial update:** `{'tags': {'environment': 'staging'}}` only updates - specified tags. - - **Mixed operations:** - `{'tags': {'environment': 'production', '`cost_center`': 'engineering', '`deprecated_tag`': null}}` - adds/updates 'environment' and '`cost_center`' while removing - '`deprecated_tag`', preserving other existing tags. - - **Replace all:** first delete existing tags with null values, then add new - ones in the same request. - extra_headers: Send extra headers +class AsyncVolumesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVolumesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. - extra_query: Add additional query parameters to the request + For more information, see https://www.github.com/G-Core/gcore-python#accessing-raw-response-data-eg-headers + """ + return AsyncVolumesResourceWithRawResponse(self) - extra_body: Add additional JSON properties to the request + @cached_property + def with_streaming_response(self) -> AsyncVolumesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. - timeout: Override the client-level default timeout for this request, in seconds + For more information, see https://www.github.com/G-Core/gcore-python#with_streaming_response """ - 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 volume_id: - raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") - return await self._patch( - f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}", - body=await async_maybe_transform( - { - "name": name, - "tags": tags, - }, - volume_update_params.VolumeUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Volume, - ) + return AsyncVolumesResourceWithStreamingResponse(self) - def list( + @overload + async def create( self, *, project_id: int | None = None, region_id: int | None = None, - bootable: bool | NotGiven = NOT_GIVEN, - cluster_id: str | NotGiven = NOT_GIVEN, - has_attachments: bool | NotGiven = NOT_GIVEN, - id_part: str | NotGiven = NOT_GIVEN, - instance_id: str | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - name_part: str | NotGiven = NOT_GIVEN, - offset: int | NotGiven = NOT_GIVEN, - tag_key: SequenceNotStr[str] | NotGiven = NOT_GIVEN, - tag_key_value: str | NotGiven = NOT_GIVEN, + image_id: str, + name: str, + size: int, + source: Literal["image"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, # 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, - ) -> AsyncPaginator[Volume, AsyncOffsetPage[Volume]]: - """Retrieve a list of volumes in the project and region. + ) -> TaskIDList: + """Create a new volume in the project and region. - The list can be filtered - by various parameters like bootable status, metadata/tags, attachments, instance - ID, name, and ID. + The volume can be created from + scratch, from an image, or from a snapshot. Optionally attach the volume to an + instance during creation. Args: project_id: Project ID region_id: Region ID - bootable: Filter by bootable field - - cluster_id: Filter volumes by k8s cluster ID + image_id: Image ID - has_attachments: Filter by the presence of attachments + name: Volume name - id_part: Filter the volume list result by the ID part of the volume + size: Volume size in GiB - instance_id: Filter volumes by instance ID + source: Volume source type - limit: Optional. Limit the number of returned items + attachment_tag: Block device attachment tag (not exposed in the user tags). Only used in + conjunction with `instance_id_to_attach_to` - name_part: Filter volumes by `name_part` inclusion in volume name.Any substring can be used - and volumes will be returned with names containing the substring. + instance_id_to_attach_to: `instance_id` to attach newly-created volume to - offset: Optional. Offset value is used to exclude the first set of records from the - result + lifecycle_policy_ids: List of lifecycle policy IDs (snapshot creation schedules) to associate with the + volume - tag_key: Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2 + tags: Key-value tags to associate 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. - tag_key_value: Optional. Filter by tag key-value pairs. + type_name: Volume type. Defaults to `standard`. If not specified for source `snapshot`, + volume type will be derived from the snapshot volume. + + 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 + """ + ... + + @overload + async def create( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + name: str, + snapshot_id: str, + source: Literal["snapshot"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + size: int | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> TaskIDList: + """Create a new volume in the project and region. + + The volume can be created from + scratch, from an image, or from a snapshot. Optionally attach the volume to an + instance during creation. + + Args: + project_id: Project ID + + region_id: Region ID + + name: Volume name + + snapshot_id: Snapshot ID + + source: Volume source type + + attachment_tag: Block device attachment tag (not exposed in the user tags). Only used in + conjunction with `instance_id_to_attach_to` + + instance_id_to_attach_to: `instance_id` to attach newly-created volume to + + lifecycle_policy_ids: List of lifecycle policy IDs (snapshot creation schedules) to associate with the + volume + + size: Volume size in GiB. If specified, value must be equal to respective snapshot + size + + tags: Key-value tags to associate 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. + + type_name: Volume type. Defaults to `standard`. If not specified for source `snapshot`, + volume type will be derived from the snapshot volume. + + 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 + """ + ... + + @overload + async def create( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + name: str, + size: int, + source: Literal["new-volume"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> TaskIDList: + """Create a new volume in the project and region. + + The volume can be created from + scratch, from an image, or from a snapshot. Optionally attach the volume to an + instance during creation. + + Args: + project_id: Project ID + + region_id: Region ID + + name: Volume name + + size: Volume size in GiB + + source: Volume source type + + attachment_tag: Block device attachment tag (not exposed in the user tags). Only used in + conjunction with `instance_id_to_attach_to` + + instance_id_to_attach_to: `instance_id` to attach newly-created volume to + + lifecycle_policy_ids: List of lifecycle policy IDs (snapshot creation schedules) to associate with the + volume + + tags: Key-value tags to associate 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. + + type_name: Volume type. Defaults to `standard`. If not specified for source `snapshot`, + volume type will be derived from the snapshot volume. + + 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 + """ + ... + + @required_args( + ["image_id", "name", "size", "source"], ["name", "snapshot_id", "source"], ["name", "size", "source"] + ) + async def create( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + image_id: str | NotGiven = NOT_GIVEN, + name: str, + size: int | NotGiven = NOT_GIVEN, + source: Literal["image"] | Literal["snapshot"] | Literal["new-volume"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + snapshot_id: str | NotGiven = NOT_GIVEN, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> TaskIDList: + 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() + return await self._post( + f"/cloud/v1/volumes/{project_id}/{region_id}", + body=await async_maybe_transform( + { + "image_id": image_id, + "name": name, + "size": size, + "source": source, + "attachment_tag": attachment_tag, + "instance_id_to_attach_to": instance_id_to_attach_to, + "lifecycle_policy_ids": lifecycle_policy_ids, + "tags": tags, + "type_name": type_name, + "snapshot_id": snapshot_id, + }, + volume_create_params.VolumeCreateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TaskIDList, + ) + + async def update( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + name: str | NotGiven = NOT_GIVEN, + tags: Optional[TagUpdateMapParam] | NotGiven = NOT_GIVEN, + # 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, + ) -> Volume: + """ + Rename a volume or update tags + + Args: + project_id: Project ID + + region_id: Region ID + + volume_id: Volume ID + + name: Name + + tags: Update key-value tags using JSON Merge Patch semantics (RFC 7386). Provide + key-value pairs to add or update tags. Set tag values to `null` to remove tags. + Unspecified tags remain unchanged. Read-only tags are always preserved and + cannot be modified. **Examples:** + + - **Add/update tags:** + `{'tags': {'environment': 'production', 'team': 'backend'}}` adds new tags or + updates existing ones. + - **Delete tags:** `{'tags': {'`old_tag`': null}}` removes specific tags. + - **Remove all tags:** `{'tags': null}` removes all user-managed tags (read-only + tags are preserved). + - **Partial update:** `{'tags': {'environment': 'staging'}}` only updates + specified tags. + - **Mixed operations:** + `{'tags': {'environment': 'production', '`cost_center`': 'engineering', '`deprecated_tag`': null}}` + adds/updates 'environment' and '`cost_center`' while removing + '`deprecated_tag`', preserving other existing tags. + - **Replace all:** first delete existing tags with null values, then add new + ones in the same request. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not volume_id: + raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") + return await self._patch( + f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}", + body=await async_maybe_transform( + { + "name": name, + "tags": tags, + }, + volume_update_params.VolumeUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Volume, + ) + + def list( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + bootable: bool | NotGiven = NOT_GIVEN, + cluster_id: str | NotGiven = NOT_GIVEN, + has_attachments: bool | NotGiven = NOT_GIVEN, + id_part: str | NotGiven = NOT_GIVEN, + instance_id: str | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + name_part: str | NotGiven = NOT_GIVEN, + offset: int | NotGiven = NOT_GIVEN, + tag_key: SequenceNotStr[str] | NotGiven = NOT_GIVEN, + tag_key_value: str | NotGiven = NOT_GIVEN, + # 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, + ) -> AsyncPaginator[Volume, AsyncOffsetPage[Volume]]: + """Retrieve a list of volumes in the project and region. + + The list can be filtered + by various parameters like bootable status, metadata/tags, attachments, instance + ID, name, and ID. + + Args: + project_id: Project ID + + region_id: Region ID + + bootable: Filter by bootable field + + cluster_id: Filter volumes by k8s cluster ID + + has_attachments: Filter by the presence of attachments + + id_part: Filter the volume list result by the ID part of the volume + + instance_id: Filter volumes by instance ID + + limit: Optional. Limit the number of returned items + + name_part: Filter volumes by `name_part` inclusion in volume name.Any substring can be used + and volumes will be returned with names containing the substring. + + offset: Optional. Offset value is used to exclude the first set of records from the + result + + tag_key: Optional. Filter by tag keys. ?`tag_key`=key1&`tag_key`=key2 + + tag_key_value: Optional. Filter by tag key-value pairs. extra_headers: Send extra headers @@ -1511,28 +1932,280 @@ async def detach_from_instance( cast_to=TaskIDList, ) - async def get( + async def get( + self, + volume_id: str, + *, + project_id: int | None = None, + region_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, + ) -> Volume: + """ + Retrieve detailed information about a specific volume. + + Args: + project_id: Project ID + + region_id: Region ID + + volume_id: Volume ID + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not volume_id: + raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") + return await self._get( + f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Volume, + ) + + async def resize( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + size: int, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> TaskIDList: + """Increase the size of a volume. + + The new size must be greater than the current + size. + + Args: + project_id: Project ID + + region_id: Region ID + + volume_id: Volume ID + + size: New volume size in GiB + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not volume_id: + raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") + return await self._post( + f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}/extend", + body=await async_maybe_transform({"size": size}, volume_resize_params.VolumeResizeParams), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=TaskIDList, + ) + + async def revert_to_last_snapshot( + self, + volume_id: str, + *, + project_id: int | None = None, + region_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, + ) -> None: + """Revert a volume to its last snapshot. + + The volume must be in an available state + to be reverted. + + Args: + project_id: Project ID + + region_id: Region ID + + volume_id: Volume ID + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if project_id is None: + project_id = self._client._get_cloud_project_id_path_param() + if region_id is None: + region_id = self._client._get_cloud_region_id_path_param() + if not volume_id: + raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}/revert", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + @overload + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + image_id: str, + name: str, + size: int, + source: Literal["image"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> Volume: + """Create a new volume in the project and region and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + The volume can be created from + scratch, from an image, or from a snapshot. Optionally attach the volume to an + instance during creation. + + Args: + project_id: Project ID + + region_id: Region ID + + image_id: Image ID + + name: Volume name + + size: Volume size in GiB + + source: Volume source type + + attachment_tag: Block device attachment tag (not exposed in the user tags). Only used in + conjunction with `instance_id_to_attach_to` + + instance_id_to_attach_to: `instance_id` to attach newly-created volume to + + lifecycle_policy_ids: List of lifecycle policy IDs (snapshot creation schedules) to associate with the + volume + + tags: Key-value tags to associate 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. + + type_name: Volume type. Defaults to `standard`. If not specified for source `snapshot`, + volume type will be derived from the snapshot volume. + + 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 + """ + ... + + @overload + async def create_and_poll( self, - volume_id: str, *, project_id: int | None = None, region_id: int | None = None, + name: str, + snapshot_id: str, + source: Literal["snapshot"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + size: int | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # 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, ) -> Volume: - """ - Retrieve detailed information about a specific volume. + """Create a new volume in the project and region and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + + The volume can be created from + scratch, from an image, or from a snapshot. Optionally attach the volume to an + instance during creation. Args: project_id: Project ID region_id: Region ID - volume_id: Volume ID + name: Volume name + + snapshot_id: Snapshot ID + + source: Volume source type + + attachment_tag: Block device attachment tag (not exposed in the user tags). Only used in + conjunction with `instance_id_to_attach_to` + + instance_id_to_attach_to: `instance_id` to attach newly-created volume to + + lifecycle_policy_ids: List of lifecycle policy IDs (snapshot creation schedules) to associate with the + volume + + size: Volume size in GiB. If specified, value must be equal to respective snapshot + size + + tags: Key-value tags to associate 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. + + type_name: Volume type. Defaults to `standard`. If not specified for source `snapshot`, + volume type will be derived from the snapshot volume. extra_headers: Send extra headers @@ -1542,47 +2215,64 @@ async def get( timeout: Override the client-level default timeout for this request, in seconds """ - if project_id is None: - project_id = self._client._get_cloud_project_id_path_param() - if region_id is None: - region_id = self._client._get_cloud_region_id_path_param() - if not volume_id: - raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") - return await self._get( - f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Volume, - ) + ... - async def resize( + @overload + async def create_and_poll( self, - volume_id: str, *, project_id: int | None = None, region_id: int | None = None, + name: str, size: int, + source: Literal["new-volume"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> TaskIDList: - """Increase the size of a volume. + ) -> Volume: + """Create a new volume in the project and region and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. - The new size must be greater than the current - size. + The volume can be created from + scratch, from an image, or from a snapshot. Optionally attach the volume to an + instance during creation. Args: project_id: Project ID region_id: Region ID - volume_id: Volume ID + name: Volume name - size: New volume size in GiB + size: Volume size in GiB + + source: Volume source type + + attachment_tag: Block device attachment tag (not exposed in the user tags). Only used in + conjunction with `instance_id_to_attach_to` + + instance_id_to_attach_to: `instance_id` to attach newly-created volume to + + lifecycle_policy_ids: List of lifecycle policy IDs (snapshot creation schedules) to associate with the + volume + + tags: Key-value tags to associate 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. + + type_name: Volume type. Defaults to `standard`. If not specified for source `snapshot`, + volume type will be derived from the snapshot volume. extra_headers: Send extra headers @@ -1592,67 +2282,219 @@ async def resize( timeout: Override the client-level default timeout for this request, in seconds """ - if project_id is None: - project_id = self._client._get_cloud_project_id_path_param() - if region_id is None: - region_id = self._client._get_cloud_region_id_path_param() - if not volume_id: - raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") - return await self._post( - f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}/extend", - body=await async_maybe_transform({"size": size}, volume_resize_params.VolumeResizeParams), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=TaskIDList, + ... + + @required_args( + ["image_id", "name", "size", "source"], ["name", "snapshot_id", "source"], ["name", "size", "source"] + ) + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + image_id: str | NotGiven = NOT_GIVEN, + name: str, + size: int | NotGiven = NOT_GIVEN, + source: Literal["image"] | Literal["snapshot"] | Literal["new-volume"], + attachment_tag: str | NotGiven = NOT_GIVEN, + instance_id_to_attach_to: str | NotGiven = NOT_GIVEN, + lifecycle_policy_ids: Iterable[int] | NotGiven = NOT_GIVEN, + tags: TagUpdateMapParam | NotGiven = NOT_GIVEN, + type_name: Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"] + | NotGiven = NOT_GIVEN, + snapshot_id: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> Volume: + """Create a new volume in the project and region 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: TaskIDList = await self.create( # type: ignore + project_id=project_id, + region_id=region_id, + image_id=image_id, + name=name, + size=size, + source=source, + attachment_tag=attachment_tag, + instance_id_to_attach_to=instance_id_to_attach_to, + lifecycle_policy_ids=lifecycle_policy_ids, + tags=tags, + type_name=type_name, + snapshot_id=snapshot_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: # type: ignore + raise ValueError("Expected at least one task to be created") + task = await self._client.cloud.tasks.poll( + task_id=response.tasks[0], # type: ignore + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + if task.created_resources is None or task.created_resources.volumes is None or len(task.created_resources.volumes) != 1: + raise ValueError("Task completed but created_resources or volumes is missing or invalid") + created_volume_id = task.created_resources.volumes[0] + return await self.get( + volume_id=created_volume_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 revert_to_last_snapshot( + async def delete_and_poll( self, volume_id: str, *, project_id: int | None = None, region_id: int | None = None, + snapshots: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, # 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: - """Revert a volume to its last snapshot. - - The volume must be in an available state - to be reverted. - - Args: - project_id: Project ID - - region_id: Region ID - - volume_id: Volume ID - - extra_headers: Send extra headers + """Delete a volume and all its snapshots 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( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + snapshots=snapshots, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) - extra_query: Add additional query parameters to the request + async def attach_to_instance_and_poll( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + instance_id: str, + attachment_tag: str | NotGiven = NOT_GIVEN, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> None: + """Attach the volume to instance 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.attach_to_instance( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + instance_id=instance_id, + attachment_tag=attachment_tag, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) - extra_body: Add additional JSON properties to the request + async def detach_from_instance_and_poll( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + instance_id: str, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> None: + """Detach the volume from instance 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.detach_from_instance( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + instance_id=instance_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) - timeout: Override the client-level default timeout for this request, in seconds - """ - if project_id is None: - project_id = self._client._get_cloud_project_id_path_param() - if region_id is None: - region_id = self._client._get_cloud_region_id_path_param() - if not volume_id: - raise ValueError(f"Expected a non-empty value for `volume_id` but received {volume_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return await self._post( - f"/cloud/v1/volumes/{project_id}/{region_id}/{volume_id}/revert", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=NoneType, + async def resize_and_poll( + self, + volume_id: str, + *, + project_id: int | None = None, + region_id: int | None = None, + size: int, + polling_interval_seconds: int | NotGiven = NOT_GIVEN, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + # 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, + ) -> Volume: + """Increase the size of a volume and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method.""" + response = await self.resize( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + size=size, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + task_id=response.tasks[0], + extra_headers=extra_headers, + polling_interval_seconds=polling_interval_seconds, + ) + return await self.get( + volume_id=volume_id, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, ) @@ -1690,6 +2532,21 @@ def __init__(self, volumes: VolumesResource) -> None: self.revert_to_last_snapshot = to_raw_response_wrapper( volumes.revert_to_last_snapshot, ) + self.create_and_poll = to_raw_response_wrapper( + volumes.create_and_poll, + ) + self.delete_and_poll = to_raw_response_wrapper( + volumes.delete_and_poll, + ) + self.attach_to_instance_and_poll = to_raw_response_wrapper( + volumes.attach_to_instance_and_poll, + ) + self.detach_from_instance_and_poll = to_raw_response_wrapper( + volumes.detach_from_instance_and_poll, + ) + self.resize_and_poll = to_raw_response_wrapper( + volumes.resize_and_poll, + ) class AsyncVolumesResourceWithRawResponse: @@ -1726,6 +2583,21 @@ def __init__(self, volumes: AsyncVolumesResource) -> None: self.revert_to_last_snapshot = async_to_raw_response_wrapper( volumes.revert_to_last_snapshot, ) + self.create_and_poll = async_to_raw_response_wrapper( + volumes.create_and_poll, + ) + self.delete_and_poll = async_to_raw_response_wrapper( + volumes.delete_and_poll, + ) + self.attach_to_instance_and_poll = async_to_raw_response_wrapper( + volumes.attach_to_instance_and_poll, + ) + self.detach_from_instance_and_poll = async_to_raw_response_wrapper( + volumes.detach_from_instance_and_poll, + ) + self.resize_and_poll = async_to_raw_response_wrapper( + volumes.resize_and_poll, + ) class VolumesResourceWithStreamingResponse: @@ -1762,6 +2634,21 @@ def __init__(self, volumes: VolumesResource) -> None: self.revert_to_last_snapshot = to_streamed_response_wrapper( volumes.revert_to_last_snapshot, ) + self.create_and_poll = to_streamed_response_wrapper( + volumes.create_and_poll, + ) + self.delete_and_poll = to_streamed_response_wrapper( + volumes.delete_and_poll, + ) + self.attach_to_instance_and_poll = to_streamed_response_wrapper( + volumes.attach_to_instance_and_poll, + ) + self.detach_from_instance_and_poll = to_streamed_response_wrapper( + volumes.detach_from_instance_and_poll, + ) + self.resize_and_poll = to_streamed_response_wrapper( + volumes.resize_and_poll, + ) class AsyncVolumesResourceWithStreamingResponse: @@ -1798,3 +2685,18 @@ def __init__(self, volumes: AsyncVolumesResource) -> None: self.revert_to_last_snapshot = async_to_streamed_response_wrapper( volumes.revert_to_last_snapshot, ) + self.create_and_poll = async_to_streamed_response_wrapper( + volumes.create_and_poll, + ) + self.delete_and_poll = async_to_streamed_response_wrapper( + volumes.delete_and_poll, + ) + self.attach_to_instance_and_poll = async_to_streamed_response_wrapper( + volumes.attach_to_instance_and_poll, + ) + self.detach_from_instance_and_poll = async_to_streamed_response_wrapper( + volumes.detach_from_instance_and_poll, + ) + self.resize_and_poll = async_to_streamed_response_wrapper( + volumes.resize_and_poll, + ) diff --git a/src/gcore/resources/storage/storage.py b/src/gcore/resources/storage/storage.py index 4f0b3a82..9326bb88 100644 --- a/src/gcore/resources/storage/storage.py +++ b/src/gcore/resources/storage/storage.py @@ -168,14 +168,16 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Storage: - """ - Updates storage configuration such as expiration date and server alias. + """Updates storage configuration such as expiration date and server alias. + + Used for + SFTP storages. Args: - expires: ISO 8601 timestamp when the storage should expire. Leave empty to remove - expiration. + expires: Duration when the storage should expire in format like "1 years 6 months 2 weeks + 3 days 5 hours 10 minutes 15 seconds". Set empty to remove expiration. - server_alias: Custom domain alias for accessing the storage. Leave empty to remove alias. + server_alias: Custom domain alias for accessing the storage. Set empty to remove alias. extra_headers: Send extra headers @@ -185,8 +187,8 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - f"/storage/provisioning/v1/storage/{storage_id}", + return self._patch( + f"/storage/provisioning/v2/storage/{storage_id}", body=maybe_transform( { "expires": expires, @@ -568,14 +570,16 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> Storage: - """ - Updates storage configuration such as expiration date and server alias. + """Updates storage configuration such as expiration date and server alias. + + Used for + SFTP storages. Args: - expires: ISO 8601 timestamp when the storage should expire. Leave empty to remove - expiration. + expires: Duration when the storage should expire in format like "1 years 6 months 2 weeks + 3 days 5 hours 10 minutes 15 seconds". Set empty to remove expiration. - server_alias: Custom domain alias for accessing the storage. Leave empty to remove alias. + server_alias: Custom domain alias for accessing the storage. Set empty to remove alias. extra_headers: Send extra headers @@ -585,8 +589,8 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - f"/storage/provisioning/v1/storage/{storage_id}", + return await self._patch( + f"/storage/provisioning/v2/storage/{storage_id}", body=await async_maybe_transform( { "expires": expires, diff --git a/src/gcore/types/storage/storage_update_params.py b/src/gcore/types/storage/storage_update_params.py index 5454b294..c7127a28 100644 --- a/src/gcore/types/storage/storage_update_params.py +++ b/src/gcore/types/storage/storage_update_params.py @@ -9,10 +9,10 @@ class StorageUpdateParams(TypedDict, total=False): expires: str - """ISO 8601 timestamp when the storage should expire. - - Leave empty to remove expiration. + """ + Duration when the storage should expire in format like "1 years 6 months 2 weeks + 3 days 5 hours 10 minutes 15 seconds". Set empty to remove expiration. """ server_alias: str - """Custom domain alias for accessing the storage. Leave empty to remove alias.""" + """Custom domain alias for accessing the storage. Set empty to remove alias.""" diff --git a/tests/api_resources/test_storage.py b/tests/api_resources/test_storage.py index 3a087e65..b66d14c9 100644 --- a/tests/api_resources/test_storage.py +++ b/tests/api_resources/test_storage.py @@ -79,7 +79,7 @@ def test_method_update(self, client: Gcore) -> None: def test_method_update_with_all_params(self, client: Gcore) -> None: storage = client.storage.update( storage_id=0, - expires="2026-12-31 23:59:59+00:00", + expires="1 years 6 months", server_alias="my-storage.company.com", ) assert_matches_type(Storage, storage, path=["response"]) @@ -383,7 +383,7 @@ async def test_method_update(self, async_client: AsyncGcore) -> None: async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> None: storage = await async_client.storage.update( storage_id=0, - expires="2026-12-31 23:59:59+00:00", + expires="1 years 6 months", server_alias="my-storage.company.com", ) assert_matches_type(Storage, storage, path=["response"])