diff --git a/.agents/skills/release/SKILL.md b/.agents/skills/release/SKILL.md index 71f5b371..e956f2d6 100644 --- a/.agents/skills/release/SKILL.md +++ b/.agents/skills/release/SKILL.md @@ -153,6 +153,12 @@ We're excited to announce version {VERSION}! changes. Skip noise. - **Omit `codegen metadata` and `aggregated API specs update`** entries unless they introduce a specific user-visible change visible in the diff. +- **Omit doc-only changes** — if a diff only updates comments, docstrings, or + descriptions (e.g., `"IPv4"` → `"IPv4 or IPv6"` in a docstring, or adding a + description to a service/resource for Terraform enablement) with no API + behavioral change (no new fields, methods, types, or changed signatures), + do **not** surface it in Part 1. These are internal metadata updates, not + user-facing SDK changes. Display the generated Part 1 to the user. Ask if they want to edit or approve. diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 52afe059..fe87cd91 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.42.0" + ".": "0.43.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index f3ae73c6..157020e8 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 657 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-e8141952a77a5faabf98feca4f964baaef5b84aa66d785c8b35357e48928df9c.yml -openapi_spec_hash: a5ff6021de586dafea9df886214ceb2c -config_hash: 0b9fe3822aef92ebe43ad609b931bfc8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-ba1eb162beed31f03386720005830bdaad3be08b9e08a07b2f10a2dd124648b8.yml +openapi_spec_hash: 416a4779f456d266b805fef5a54cfbc7 +config_hash: d2defa3f4caff86ec6f4b8890f74b295 diff --git a/CHANGELOG.md b/CHANGELOG.md index 410c7460..26bac9ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 0.43.0 (2026-04-22) + +Full Changelog: [v0.42.0...v0.43.0](https://github.com/G-Core/gcore-python/compare/v0.42.0...v0.43.0) + +### Features + +* **api:** aggregated API specs update ([7d70fe4](https://github.com/G-Core/gcore-python/commit/7d70fe4b2ef3fc2ba54cb8f2a195a5b0f09514bc)) +* **api:** aggregated API specs update ([2e269b6](https://github.com/G-Core/gcore-python/commit/2e269b6f17a504250715f6f33316818e25ce6d70)) +* **api:** aggregated API specs update ([96cd4c9](https://github.com/G-Core/gcore-python/commit/96cd4c96ff40b2d2bfc0d220309eadbc88b0dbb3)) +* **api:** aggregated API specs update ([8b6d277](https://github.com/G-Core/gcore-python/commit/8b6d2777aa8967ba88788145440b9e6e94c7c7f7)) +* **cloud:** add examples for GPU baremetal cluster images ([7279536](https://github.com/G-Core/gcore-python/commit/7279536d11b4163c445e270180da5de8582709ee)) +* **cloud:** add polling methods for K8s clusters and pools ([76bd4c9](https://github.com/G-Core/gcore-python/commit/76bd4c9a97c4da20f7dcf4f04f87e195278527a0)) +* **cloud:** add runnable K8s cluster example ([030f335](https://github.com/G-Core/gcore-python/commit/030f335f902c3cb2b29792b22b9e9c0a2f6f6de7)) +* **storage:** support Terraform generation for locations ([063f1f4](https://github.com/G-Core/gcore-python/commit/063f1f40ee0d10153f5437a69f227936516cf8e7)) + + +### Bug Fixes + +* add rule to omit doc-only changes from release notes ([45bae59](https://github.com/G-Core/gcore-python/commit/45bae593f219bc62fd3773b97db603b4859500d7)) + + +### Performance Improvements + +* **client:** optimize file structure copying in multipart requests ([2fd1bcb](https://github.com/G-Core/gcore-python/commit/2fd1bcbd057214f9fb494484e99d203a24c04c29)) + + +### Chores + +* suppress deprecation warning on IAM api_tokens.create usage ([9b2f4a4](https://github.com/G-Core/gcore-python/commit/9b2f4a4ff19039df9dc1aac3dfdc5eba000de025)) +* **tests:** bump steady to v0.22.1 ([6ac2648](https://github.com/G-Core/gcore-python/commit/6ac2648d93f24b1f23544dac36d979575c763f3c)) + ## 0.42.0 (2026-04-17) Full Changelog: [v0.41.0...v0.42.0](https://github.com/G-Core/gcore-python/compare/v0.41.0...v0.42.0) diff --git a/examples/cloud/gpu_baremetal_cluster_images.py b/examples/cloud/gpu_baremetal_cluster_images.py new file mode 100644 index 00000000..e456be90 --- /dev/null +++ b/examples/cloud/gpu_baremetal_cluster_images.py @@ -0,0 +1,91 @@ +from typing import List + +from gcore import Gcore +from gcore.types.cloud.gpu_image import GPUImage + + +def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + + gcore = Gcore( + # No need to explicitly pass to Gcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # List existing images + list_images(client=gcore) + + # Upload a new image + image = upload_image(client=gcore) + + # Get the newly uploaded image + get_image(client=gcore, image_id=image.id) + + # Delete the image + delete_image(client=gcore, image_id=image.id) + + +def list_images(*, client: Gcore) -> List[GPUImage]: + print("\n=== LIST GPU BAREMETAL CLUSTER IMAGES ===") + images = client.cloud.gpu_baremetal.clusters.images.list() + _print_image_details(images.results) + print(f"Total GPU baremetal cluster images: {len(images.results)}") + print("========================") + return images.results + + +def upload_image(*, client: Gcore) -> GPUImage: + print("\n=== UPLOAD GPU BAREMETAL CLUSTER IMAGE ===") + image = client.cloud.gpu_baremetal.clusters.images.upload_and_poll( + name="gcore-python-example-gpu-baremetal-image", + url="http://mirror.noris.net/cirros/0.4.0/cirros-0.4.0-x86_64-disk.img", + architecture="x86_64", + os_type="linux", + ssh_key="allow", + tags={"name": "gcore-python-example"}, + ) + print(f"Uploaded image: ID={image.id}, name={image.name}, status={image.status}") + print("========================") + return image + + +def get_image(*, client: Gcore, image_id: str) -> GPUImage: + print("\n=== GET GPU BAREMETAL CLUSTER IMAGE ===") + image = client.cloud.gpu_baremetal.clusters.images.get(image_id=image_id) + print(f"Image: ID={image.id}, name={image.name}, status={image.status}") + print(f"OS type: {image.os_type}, architecture: {image.architecture}") + print(f"Min RAM: {image.min_ram} MB, Min Disk: {image.min_disk} GB") + print(f"Visibility: {image.visibility}") + print("========================") + return image + + +def delete_image(*, client: Gcore, image_id: str) -> None: + print("\n=== DELETE GPU BAREMETAL CLUSTER IMAGE ===") + client.cloud.gpu_baremetal.clusters.images.delete_and_poll(image_id=image_id) + print(f"Deleted image: ID={image_id}") + print("========================") + + +def _print_image_details(images: List[GPUImage]) -> None: + display_count = 3 + if len(images) < display_count: + display_count = len(images) + + for i in range(display_count): + img = images[i] + print(f" {i + 1}. Image ID: {img.id}, name: {img.name}, OS type: {img.os_type}, status: {img.status}") + + if len(images) > display_count: + print(f" ... and {len(images) - display_count} more images") + + +if __name__ == "__main__": + main() diff --git a/examples/cloud/gpu_baremetal_cluster_images_async.py b/examples/cloud/gpu_baremetal_cluster_images_async.py new file mode 100644 index 00000000..5bfc8514 --- /dev/null +++ b/examples/cloud/gpu_baremetal_cluster_images_async.py @@ -0,0 +1,92 @@ +import asyncio +from typing import List + +from gcore import AsyncGcore +from gcore.types.cloud.gpu_image import GPUImage + + +async def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + + gcore = AsyncGcore( + # No need to explicitly pass to AsyncGcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # List existing images + await list_images(client=gcore) + + # Upload a new image + image = await upload_image(client=gcore) + + # Get the newly uploaded image + await get_image(client=gcore, image_id=image.id) + + # Delete the image + await delete_image(client=gcore, image_id=image.id) + + +async def list_images(*, client: AsyncGcore) -> List[GPUImage]: + print("\n=== LIST GPU BAREMETAL CLUSTER IMAGES ===") + images = await client.cloud.gpu_baremetal.clusters.images.list() + _print_image_details(images.results) + print(f"Total GPU baremetal cluster images: {len(images.results)}") + print("========================") + return images.results + + +async def upload_image(*, client: AsyncGcore) -> GPUImage: + print("\n=== UPLOAD GPU BAREMETAL CLUSTER IMAGE ===") + image = await client.cloud.gpu_baremetal.clusters.images.upload_and_poll( + name="gcore-python-example-gpu-baremetal-image", + url="http://mirror.noris.net/cirros/0.4.0/cirros-0.4.0-x86_64-disk.img", + architecture="x86_64", + os_type="linux", + ssh_key="allow", + tags={"name": "gcore-python-example"}, + ) + print(f"Uploaded image: ID={image.id}, name={image.name}, status={image.status}") + print("========================") + return image + + +async def get_image(*, client: AsyncGcore, image_id: str) -> GPUImage: + print("\n=== GET GPU BAREMETAL CLUSTER IMAGE ===") + image = await client.cloud.gpu_baremetal.clusters.images.get(image_id=image_id) + print(f"Image: ID={image.id}, name={image.name}, status={image.status}") + print(f"OS type: {image.os_type}, architecture: {image.architecture}") + print(f"Min RAM: {image.min_ram} MB, Min Disk: {image.min_disk} GB") + print(f"Visibility: {image.visibility}") + print("========================") + return image + + +async def delete_image(*, client: AsyncGcore, image_id: str) -> None: + print("\n=== DELETE GPU BAREMETAL CLUSTER IMAGE ===") + await client.cloud.gpu_baremetal.clusters.images.delete_and_poll(image_id=image_id) + print(f"Deleted image: ID={image_id}") + print("========================") + + +def _print_image_details(images: List[GPUImage]) -> None: + display_count = 3 + if len(images) < display_count: + display_count = len(images) + + for i in range(display_count): + img = images[i] + print(f" {i + 1}. Image ID: {img.id}, name: {img.name}, OS type: {img.os_type}, status: {img.status}") + + if len(images) > display_count: + print(f" ... and {len(images) - display_count} more images") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/cloud/k8s_clusters.py b/examples/cloud/k8s_clusters.py new file mode 100644 index 00000000..acd40038 --- /dev/null +++ b/examples/cloud/k8s_clusters.py @@ -0,0 +1,384 @@ +import os +import time +from typing import List + +from gcore import Gcore +from gcore.types.cloud.baremetal_flavor import BaremetalFlavor +from gcore.types.cloud.k8s_cluster_version import K8SClusterVersion +from gcore.types.cloud.k8s.cluster_create_params import Pool + + +def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + # TODO set a pre-existing SSH keypair name before running. The K8s cluster + # create endpoint requires a keypair to bootstrap nodes. + keypair_name = os.environ["GCORE_CLOUD_K8S_KEYPAIR_NAME"] + + gcore = Gcore( + # No need to explicitly pass to Gcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # Look up a small non-GPU flavor and the latest version to use for cluster creation. + flavors = list_flavors(client=gcore) + pool_flavor_id = _pick_pool_flavor(flavors) + + versions = list_versions(client=gcore) + cluster_version = _pick_create_version(versions) + + # Cluster lifecycle. + cluster_name = create_cluster( + client=gcore, + flavor_id=pool_flavor_id, + version=cluster_version, + keypair_name=keypair_name, + ) + + get_cluster(client=gcore, cluster_name=cluster_name) + list_clusters(client=gcore) + update_cluster(client=gcore, cluster_name=cluster_name) + + # Upgrade the cluster to the newest available version, if one exists. + upgrade_versions = list_upgrade_versions(client=gcore, cluster_name=cluster_name) + if upgrade_versions: + upgrade_cluster(client=gcore, cluster_name=cluster_name, target_version=upgrade_versions[-1].version) + # The upgrade task finishes before the cluster itself settles back to + # Provisioned, so subsequent mutations (e.g. creating a new pool) can + # be rejected with "Cluster or pool is not ready". Wait it out. + wait_for_cluster_ready(client=gcore, cluster_name=cluster_name) + else: + print(f"No upgrade versions available for cluster {cluster_name}; skipping upgrade") + + get_certificate(client=gcore, cluster_name=cluster_name) + get_kubeconfig(client=gcore, cluster_name=cluster_name) + + # Pool operations. + list_pools(client=gcore, cluster_name=cluster_name) + first_pool = _get_first_pool(client=gcore, cluster_name=cluster_name) + + check_pool_quota(client=gcore, flavor_id=pool_flavor_id) + second_pool = create_pool(client=gcore, cluster_name=cluster_name, flavor_id=pool_flavor_id) + update_pool(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + resize_pool(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + list_pool_nodes(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + delete_pool(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + + # Ensure the initial pool is still reachable after the second pool is gone. + get_pool(client=gcore, cluster_name=cluster_name, pool_name=first_pool) + + list_cluster_nodes(client=gcore, cluster_name=cluster_name) + + # Final cleanup. + delete_cluster(client=gcore, cluster_name=cluster_name) + + +def create_cluster(*, client: Gcore, flavor_id: str, version: str, keypair_name: str) -> str: + print("\n=== CREATE K8S CLUSTER ===") + cluster = client.cloud.k8s.clusters.create_and_poll( + name="gcore-py-example-k8s", + keypair=keypair_name, + version=version, + pools=[ + Pool( + name="gcore-python-pool", + flavor_id=flavor_id, + min_node_count=1, + max_node_count=2, + boot_volume_size=50, + boot_volume_type="standard", + auto_healing_enabled=True, + is_public_ipv4=True, + servergroup_policy="soft-anti-affinity", + labels={"pool": "gcore-python-example"}, + ), + ], + ) + print(f"Created K8s cluster: name={cluster.name}, version={cluster.version}, status={cluster.status}") + print("==========================") + return cluster.name + + +def get_cluster(*, client: Gcore, cluster_name: str) -> None: + print("\n=== GET K8S CLUSTER ===") + cluster = client.cloud.k8s.clusters.get(cluster_name=cluster_name) + print( + f"K8s cluster: name={cluster.name}, version={cluster.version}, " + f"status={cluster.status}, pools={len(cluster.pools)}" + ) + print("=======================") + + +def list_clusters(*, client: Gcore) -> None: + print("\n=== LIST K8S CLUSTERS ===") + clusters = client.cloud.k8s.clusters.list() + if not clusters.results: + print(" No k8s clusters found.") + for count, cluster in enumerate(clusters.results, 1): + print(f" {count}. K8s cluster: name={cluster.name}, version={cluster.version}, status={cluster.status}") + print("=========================") + + +def update_cluster(*, client: Gcore, cluster_name: str) -> None: + print("\n=== UPDATE K8S CLUSTER ===") + # Override a cluster-autoscaler parameter — a safe, idempotent change that + # exercises update_and_poll. + cluster = client.cloud.k8s.clusters.update_and_poll( + cluster_name=cluster_name, + autoscaler_config={"scale-down-unneeded-time": "5m"}, + ) + print(f"Updated K8s cluster: name={cluster.name}, autoscaler_config={cluster.autoscaler_config}") + print("==========================") + + +def list_upgrade_versions(*, client: Gcore, cluster_name: str) -> List[K8SClusterVersion]: + print("\n=== LIST K8S CLUSTER UPGRADE VERSIONS ===") + versions = client.cloud.k8s.clusters.list_versions_for_upgrade(cluster_name=cluster_name) + for count, version in enumerate(versions.results, 1): + print(f" {count}. Upgrade version: {version.version}") + if not versions.results: + print(" Cluster is already on the latest available version.") + print("==========================================") + return versions.results + + +def upgrade_cluster(*, client: Gcore, cluster_name: str, target_version: str) -> None: + print("\n=== UPGRADE K8S CLUSTER ===") + cluster = client.cloud.k8s.clusters.upgrade_and_poll(cluster_name=cluster_name, version=target_version) + print(f"Upgraded K8s cluster: name={cluster.name}, version={cluster.version}") + print("===========================") + + +def get_certificate(*, client: Gcore, cluster_name: str) -> None: + print("\n=== GET K8S CLUSTER CERTIFICATE ===") + cert = client.cloud.k8s.clusters.get_certificate(cluster_name=cluster_name) + print(f"Certificate bytes={len(cert.certificate)}, Key bytes={len(cert.key)}") + print("===================================") + + +def wait_for_cluster_ready(*, client: Gcore, cluster_name: str) -> None: + print("\n=== WAIT FOR K8S CLUSTER READY ===") + poll_interval = 10 + max_attempts = 60 + for attempt in range(1, max_attempts + 1): + cluster = client.cloud.k8s.clusters.get(cluster_name=cluster_name) + pending: List[str] = [] + if cluster.status != "Provisioned": + pending.append(f"cluster={cluster.status}") + for pool in cluster.pools: + if pool.status != "Running": + pending.append(f"pool[{pool.name}]={pool.status}") + if not pending: + print(f"Cluster {cluster_name} is ready (all pools Running)") + print("==================================") + return + print(f" attempt {attempt}: waiting on {' '.join(pending)}") + time.sleep(poll_interval) + raise TimeoutError(f"Cluster {cluster_name} did not become ready after {max_attempts} attempts") + + +def get_kubeconfig(*, client: Gcore, cluster_name: str) -> None: + print("\n=== GET K8S KUBECONFIG ===") + kubeconfig = client.cloud.k8s.clusters.kubeconfig.get(cluster_name=cluster_name) + print( + f"Kubeconfig: host={kubeconfig.host}, created_at={kubeconfig.created_at}, " + f"expires_at={kubeconfig.expires_at}, bytes={len(kubeconfig.config)}" + ) + print("==========================") + + +def list_pools(*, client: Gcore, cluster_name: str) -> None: + print("\n=== LIST K8S CLUSTER POOLS ===") + pools = client.cloud.k8s.clusters.pools.list(cluster_name=cluster_name) + if not pools.results: + print(" No pools found.") + for count, pool in enumerate(pools.results, 1): + print( + f" {count}. Pool: name={pool.name}, flavor={pool.flavor_id}, " + f"node_count={pool.node_count}, status={pool.status}" + ) + print("==============================") + + +def get_pool(*, client: Gcore, cluster_name: str, pool_name: str) -> None: + print("\n=== GET K8S CLUSTER POOL ===") + pool = client.cloud.k8s.clusters.pools.get(pool_name=pool_name, cluster_name=cluster_name) + print( + f"Pool: name={pool.name}, flavor={pool.flavor_id}, node_count={pool.node_count}, " + f"min={pool.min_node_count}, max={pool.max_node_count}, status={pool.status}" + ) + print("============================") + + +def check_pool_quota(*, client: Gcore, flavor_id: str) -> None: + print("\n=== CHECK K8S POOL QUOTA ===") + quota = client.cloud.k8s.clusters.pools.check_quota( + flavor_id=flavor_id, + name="gcore-python-pool-2", + min_node_count=1, + max_node_count=2, + boot_volume_size=50, + servergroup_policy="soft-anti-affinity", + ) + print(f"CPU: limit={quota.cpu_count_limit}, requested={quota.cpu_count_requested}, usage={quota.cpu_count_usage}") + print(f"RAM (MiB): limit={quota.ram_limit}, requested={quota.ram_requested}, usage={quota.ram_usage}") + print( + f"Volumes: count_limit={quota.volume_count_limit}, count_usage={quota.volume_count_usage}, " + f"size_limit={quota.volume_size_limit} GiB" + ) + print("============================") + + +def create_pool(*, client: Gcore, cluster_name: str, flavor_id: str) -> str: + print("\n=== CREATE K8S CLUSTER POOL ===") + pool = client.cloud.k8s.clusters.pools.create_and_poll( + cluster_name=cluster_name, + name="gcore-python-pool-2", + flavor_id=flavor_id, + min_node_count=1, + max_node_count=2, + boot_volume_size=50, + boot_volume_type="standard", + auto_healing_enabled=True, + is_public_ipv4=True, + servergroup_policy="soft-anti-affinity", + labels={"pool": "gcore-python-example-2"}, + ) + print(f"Created pool: name={pool.name}, flavor={pool.flavor_id}, node_count={pool.node_count}") + print("===============================") + return pool.name + + +def update_pool(*, client: Gcore, cluster_name: str, pool_name: str) -> None: + print("\n=== UPDATE K8S CLUSTER POOL ===") + pool = client.cloud.k8s.clusters.pools.update( + pool_name=pool_name, + cluster_name=cluster_name, + labels={"pool": "gcore-python-example-2", "stage": "updated"}, + ) + print(f"Updated pool: name={pool.name}, labels={pool.labels}") + print("===============================") + + +def resize_pool(*, client: Gcore, cluster_name: str, pool_name: str) -> None: + print("\n=== RESIZE K8S CLUSTER POOL ===") + pool = client.cloud.k8s.clusters.pools.resize_and_poll( + pool_name=pool_name, + cluster_name=cluster_name, + node_count=2, + ) + print(f"Resized pool: name={pool.name}, node_count={pool.node_count}") + print("===============================") + + +def delete_pool(*, client: Gcore, cluster_name: str, pool_name: str) -> None: + print("\n=== DELETE K8S CLUSTER POOL ===") + client.cloud.k8s.clusters.pools.delete_and_poll(pool_name=pool_name, cluster_name=cluster_name) + print(f"Deleted pool: name={pool_name}") + print("===============================") + + +def list_cluster_nodes(*, client: Gcore, cluster_name: str) -> None: + print("\n=== LIST K8S CLUSTER NODES ===") + nodes = client.cloud.k8s.clusters.nodes.list(cluster_name=cluster_name) + if not nodes.results: + print(" No nodes found.") + for count, node in enumerate(nodes.results, 1): + print(f" {count}. Node: ID={node.id}, name={node.name}, status={node.status}") + print("==============================") + + +def list_pool_nodes(*, client: Gcore, cluster_name: str, pool_name: str) -> None: + print("\n=== LIST K8S POOL NODES ===") + nodes = client.cloud.k8s.clusters.pools.nodes.list(pool_name=pool_name, cluster_name=cluster_name) + if not nodes.results: + print(" No pool nodes found.") + for count, node in enumerate(nodes.results, 1): + print(f" {count}. Node: ID={node.id}, name={node.name}, status={node.status}") + print("===========================") + + +def delete_cluster(*, client: Gcore, cluster_name: str) -> None: + print("\n=== DELETE K8S CLUSTER ===") + client.cloud.k8s.clusters.delete_and_poll(cluster_name=cluster_name) + print(f"Deleted K8s cluster: name={cluster_name}") + print("==========================") + + +def list_flavors(*, client: Gcore) -> List[BaremetalFlavor]: + print("\n=== LIST K8S FLAVORS ===") + flavors = client.cloud.k8s.flavors.list(exclude_gpu=True, include_capacity=True) + + display_count = min(3, len(flavors.results)) + for i in range(display_count): + flavor = flavors.results[i] + print( + f" {i + 1}. Flavor: ID={flavor.flavor_id}, name={flavor.flavor_name}, " + f"RAM={flavor.ram} MB, VCPUs={flavor.vcpus}" + ) + + if len(flavors.results) > display_count: + print(f" ... and {len(flavors.results) - display_count} more flavors") + + print(f"Total k8s flavors: {len(flavors.results)}") + print("========================") + return flavors.results + + +def list_versions(*, client: Gcore) -> List[K8SClusterVersion]: + print("\n=== LIST K8S VERSIONS ===") + versions = client.cloud.k8s.list_versions() + for count, version in enumerate(versions.results, 1): + print(f" {count}. Version: {version.version}") + print(f"Total k8s versions: {len(versions.results)}") + print("=========================") + return versions.results + + +def _pick_pool_flavor(flavors: List[BaremetalFlavor]) -> str: + # Prefer the smallest g1-standard VM flavor with capacity; skip disabled, + # zero-capacity, and test/fake flavors. + candidates = [ + f + for f in flavors + if not f.disabled + and (f.capacity or 0) > 0 + and "test" not in f.flavor_name + and "fake" not in f.flavor_name + and f.flavor_name.startswith("g1-standard-") + ] + if not candidates: + raise ValueError("No suitable g1-standard k8s flavor with capacity found") + + pick = min(candidates, key=lambda f: f.ram) + print(f"Selected k8s pool flavor: {pick.flavor_name} (RAM: {pick.ram} MB, VCPUs: {pick.vcpus})") + return pick.flavor_id + + +def _pick_create_version(versions: List[K8SClusterVersion]) -> str: + if not versions: + raise ValueError("No k8s versions available for cluster creation") + # The API returns versions in ascending order. Pick the penultimate + # version when possible so the example always has a newer version to + # upgrade to later. Fall back to the only available version otherwise. + idx = max(len(versions) - 2, 0) + picked = versions[idx].version + print(f"Selected k8s version for creation: {picked}") + return picked + + +def _get_first_pool(*, client: Gcore, cluster_name: str) -> str: + pools = client.cloud.k8s.clusters.pools.list(cluster_name=cluster_name) + if not pools.results: + raise ValueError(f"Cluster {cluster_name} has no pools") + return pools.results[0].name + + +if __name__ == "__main__": + main() diff --git a/examples/cloud/k8s_clusters_async.py b/examples/cloud/k8s_clusters_async.py new file mode 100644 index 00000000..824fd87c --- /dev/null +++ b/examples/cloud/k8s_clusters_async.py @@ -0,0 +1,382 @@ +import os +import asyncio +from typing import List + +from gcore import AsyncGcore +from gcore.types.cloud.baremetal_flavor import BaremetalFlavor +from gcore.types.cloud.k8s_cluster_version import K8SClusterVersion +from gcore.types.cloud.k8s.cluster_create_params import Pool + + +async def main() -> None: + # TODO set API key before running + # api_key = os.environ["GCORE_API_KEY"] + # TODO set cloud project ID before running + # cloud_project_id = os.environ["GCORE_CLOUD_PROJECT_ID"] + # TODO set cloud region ID before running + # cloud_region_id = os.environ["GCORE_CLOUD_REGION_ID"] + # TODO set a pre-existing SSH keypair name before running. The K8s cluster + # create endpoint requires a keypair to bootstrap nodes. + keypair_name = os.environ["GCORE_CLOUD_K8S_KEYPAIR_NAME"] + + gcore = AsyncGcore( + # No need to explicitly pass to AsyncGcore constructor if using environment variables + # api_key=api_key, + # cloud_project_id=cloud_project_id, + # cloud_region_id=cloud_region_id, + ) + + # Look up a small non-GPU flavor and the latest version to use for cluster creation. + flavors = await list_flavors(client=gcore) + pool_flavor_id = _pick_pool_flavor(flavors) + + versions = await list_versions(client=gcore) + cluster_version = _pick_create_version(versions) + + # Cluster lifecycle. + cluster_name = await create_cluster( + client=gcore, + flavor_id=pool_flavor_id, + version=cluster_version, + keypair_name=keypair_name, + ) + + await get_cluster(client=gcore, cluster_name=cluster_name) + await list_clusters(client=gcore) + await update_cluster(client=gcore, cluster_name=cluster_name) + + # Upgrade the cluster to the newest available version, if one exists. + upgrade_versions = await list_upgrade_versions(client=gcore, cluster_name=cluster_name) + if upgrade_versions: + await upgrade_cluster(client=gcore, cluster_name=cluster_name, target_version=upgrade_versions[-1].version) + # The upgrade task finishes before the cluster itself settles back to + # Provisioned, so subsequent mutations (e.g. creating a new pool) can + # be rejected with "Cluster or pool is not ready". Wait it out. + await wait_for_cluster_ready(client=gcore, cluster_name=cluster_name) + else: + print(f"No upgrade versions available for cluster {cluster_name}; skipping upgrade") + + await get_certificate(client=gcore, cluster_name=cluster_name) + await get_kubeconfig(client=gcore, cluster_name=cluster_name) + + # Pool operations. + await list_pools(client=gcore, cluster_name=cluster_name) + first_pool = await _get_first_pool(client=gcore, cluster_name=cluster_name) + + await check_pool_quota(client=gcore, flavor_id=pool_flavor_id) + second_pool = await create_pool(client=gcore, cluster_name=cluster_name, flavor_id=pool_flavor_id) + await update_pool(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + await resize_pool(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + await list_pool_nodes(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + await delete_pool(client=gcore, cluster_name=cluster_name, pool_name=second_pool) + + # Ensure the initial pool is still reachable after the second pool is gone. + await get_pool(client=gcore, cluster_name=cluster_name, pool_name=first_pool) + + await list_cluster_nodes(client=gcore, cluster_name=cluster_name) + + # Final cleanup. + await delete_cluster(client=gcore, cluster_name=cluster_name) + + +async def create_cluster(*, client: AsyncGcore, flavor_id: str, version: str, keypair_name: str) -> str: + print("\n=== CREATE K8S CLUSTER ===") + cluster = await client.cloud.k8s.clusters.create_and_poll( + name="gcore-py-example-k8s", + keypair=keypair_name, + version=version, + pools=[ + Pool( + name="gcore-python-pool", + flavor_id=flavor_id, + min_node_count=1, + max_node_count=2, + boot_volume_size=50, + boot_volume_type="standard", + auto_healing_enabled=True, + is_public_ipv4=True, + servergroup_policy="soft-anti-affinity", + labels={"pool": "gcore-python-example"}, + ), + ], + ) + print(f"Created K8s cluster: name={cluster.name}, version={cluster.version}, status={cluster.status}") + print("==========================") + return cluster.name + + +async def get_cluster(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== GET K8S CLUSTER ===") + cluster = await client.cloud.k8s.clusters.get(cluster_name=cluster_name) + print( + f"K8s cluster: name={cluster.name}, version={cluster.version}, " + f"status={cluster.status}, pools={len(cluster.pools)}" + ) + print("=======================") + + +async def list_clusters(*, client: AsyncGcore) -> None: + print("\n=== LIST K8S CLUSTERS ===") + clusters = await client.cloud.k8s.clusters.list() + if not clusters.results: + print(" No k8s clusters found.") + for count, cluster in enumerate(clusters.results, 1): + print(f" {count}. K8s cluster: name={cluster.name}, version={cluster.version}, status={cluster.status}") + print("=========================") + + +async def update_cluster(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== UPDATE K8S CLUSTER ===") + cluster = await client.cloud.k8s.clusters.update_and_poll( + cluster_name=cluster_name, + autoscaler_config={"scale-down-unneeded-time": "5m"}, + ) + print(f"Updated K8s cluster: name={cluster.name}, autoscaler_config={cluster.autoscaler_config}") + print("==========================") + + +async def list_upgrade_versions(*, client: AsyncGcore, cluster_name: str) -> List[K8SClusterVersion]: + print("\n=== LIST K8S CLUSTER UPGRADE VERSIONS ===") + versions = await client.cloud.k8s.clusters.list_versions_for_upgrade(cluster_name=cluster_name) + for count, version in enumerate(versions.results, 1): + print(f" {count}. Upgrade version: {version.version}") + if not versions.results: + print(" Cluster is already on the latest available version.") + print("==========================================") + return versions.results + + +async def upgrade_cluster(*, client: AsyncGcore, cluster_name: str, target_version: str) -> None: + print("\n=== UPGRADE K8S CLUSTER ===") + cluster = await client.cloud.k8s.clusters.upgrade_and_poll(cluster_name=cluster_name, version=target_version) + print(f"Upgraded K8s cluster: name={cluster.name}, version={cluster.version}") + print("===========================") + + +async def get_certificate(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== GET K8S CLUSTER CERTIFICATE ===") + cert = await client.cloud.k8s.clusters.get_certificate(cluster_name=cluster_name) + print(f"Certificate bytes={len(cert.certificate)}, Key bytes={len(cert.key)}") + print("===================================") + + +async def wait_for_cluster_ready(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== WAIT FOR K8S CLUSTER READY ===") + poll_interval = 10 + max_attempts = 60 + for attempt in range(1, max_attempts + 1): + cluster = await client.cloud.k8s.clusters.get(cluster_name=cluster_name) + pending: List[str] = [] + if cluster.status != "Provisioned": + pending.append(f"cluster={cluster.status}") + for pool in cluster.pools: + if pool.status != "Running": + pending.append(f"pool[{pool.name}]={pool.status}") + if not pending: + print(f"Cluster {cluster_name} is ready (all pools Running)") + print("==================================") + return + print(f" attempt {attempt}: waiting on {' '.join(pending)}") + await asyncio.sleep(poll_interval) + raise TimeoutError(f"Cluster {cluster_name} did not become ready after {max_attempts} attempts") + + +async def get_kubeconfig(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== GET K8S KUBECONFIG ===") + kubeconfig = await client.cloud.k8s.clusters.kubeconfig.get(cluster_name=cluster_name) + print( + f"Kubeconfig: host={kubeconfig.host}, created_at={kubeconfig.created_at}, " + f"expires_at={kubeconfig.expires_at}, bytes={len(kubeconfig.config)}" + ) + print("==========================") + + +async def list_pools(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== LIST K8S CLUSTER POOLS ===") + pools = await client.cloud.k8s.clusters.pools.list(cluster_name=cluster_name) + if not pools.results: + print(" No pools found.") + for count, pool in enumerate(pools.results, 1): + print( + f" {count}. Pool: name={pool.name}, flavor={pool.flavor_id}, " + f"node_count={pool.node_count}, status={pool.status}" + ) + print("==============================") + + +async def get_pool(*, client: AsyncGcore, cluster_name: str, pool_name: str) -> None: + print("\n=== GET K8S CLUSTER POOL ===") + pool = await client.cloud.k8s.clusters.pools.get(pool_name=pool_name, cluster_name=cluster_name) + print( + f"Pool: name={pool.name}, flavor={pool.flavor_id}, node_count={pool.node_count}, " + f"min={pool.min_node_count}, max={pool.max_node_count}, status={pool.status}" + ) + print("============================") + + +async def check_pool_quota(*, client: AsyncGcore, flavor_id: str) -> None: + print("\n=== CHECK K8S POOL QUOTA ===") + quota = await client.cloud.k8s.clusters.pools.check_quota( + flavor_id=flavor_id, + name="gcore-python-pool-2", + min_node_count=1, + max_node_count=2, + boot_volume_size=50, + servergroup_policy="soft-anti-affinity", + ) + print(f"CPU: limit={quota.cpu_count_limit}, requested={quota.cpu_count_requested}, usage={quota.cpu_count_usage}") + print(f"RAM (MiB): limit={quota.ram_limit}, requested={quota.ram_requested}, usage={quota.ram_usage}") + print( + f"Volumes: count_limit={quota.volume_count_limit}, count_usage={quota.volume_count_usage}, " + f"size_limit={quota.volume_size_limit} GiB" + ) + print("============================") + + +async def create_pool(*, client: AsyncGcore, cluster_name: str, flavor_id: str) -> str: + print("\n=== CREATE K8S CLUSTER POOL ===") + pool = await client.cloud.k8s.clusters.pools.create_and_poll( + cluster_name=cluster_name, + name="gcore-python-pool-2", + flavor_id=flavor_id, + min_node_count=1, + max_node_count=2, + boot_volume_size=50, + boot_volume_type="standard", + auto_healing_enabled=True, + is_public_ipv4=True, + servergroup_policy="soft-anti-affinity", + labels={"pool": "gcore-python-example-2"}, + ) + print(f"Created pool: name={pool.name}, flavor={pool.flavor_id}, node_count={pool.node_count}") + print("===============================") + return pool.name + + +async def update_pool(*, client: AsyncGcore, cluster_name: str, pool_name: str) -> None: + print("\n=== UPDATE K8S CLUSTER POOL ===") + pool = await client.cloud.k8s.clusters.pools.update( + pool_name=pool_name, + cluster_name=cluster_name, + labels={"pool": "gcore-python-example-2", "stage": "updated"}, + ) + print(f"Updated pool: name={pool.name}, labels={pool.labels}") + print("===============================") + + +async def resize_pool(*, client: AsyncGcore, cluster_name: str, pool_name: str) -> None: + print("\n=== RESIZE K8S CLUSTER POOL ===") + pool = await client.cloud.k8s.clusters.pools.resize_and_poll( + pool_name=pool_name, + cluster_name=cluster_name, + node_count=2, + ) + print(f"Resized pool: name={pool.name}, node_count={pool.node_count}") + print("===============================") + + +async def delete_pool(*, client: AsyncGcore, cluster_name: str, pool_name: str) -> None: + print("\n=== DELETE K8S CLUSTER POOL ===") + await client.cloud.k8s.clusters.pools.delete_and_poll(pool_name=pool_name, cluster_name=cluster_name) + print(f"Deleted pool: name={pool_name}") + print("===============================") + + +async def list_cluster_nodes(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== LIST K8S CLUSTER NODES ===") + nodes = await client.cloud.k8s.clusters.nodes.list(cluster_name=cluster_name) + if not nodes.results: + print(" No nodes found.") + for count, node in enumerate(nodes.results, 1): + print(f" {count}. Node: ID={node.id}, name={node.name}, status={node.status}") + print("==============================") + + +async def list_pool_nodes(*, client: AsyncGcore, cluster_name: str, pool_name: str) -> None: + print("\n=== LIST K8S POOL NODES ===") + nodes = await client.cloud.k8s.clusters.pools.nodes.list(pool_name=pool_name, cluster_name=cluster_name) + if not nodes.results: + print(" No pool nodes found.") + for count, node in enumerate(nodes.results, 1): + print(f" {count}. Node: ID={node.id}, name={node.name}, status={node.status}") + print("===========================") + + +async def delete_cluster(*, client: AsyncGcore, cluster_name: str) -> None: + print("\n=== DELETE K8S CLUSTER ===") + await client.cloud.k8s.clusters.delete_and_poll(cluster_name=cluster_name) + print(f"Deleted K8s cluster: name={cluster_name}") + print("==========================") + + +async def list_flavors(*, client: AsyncGcore) -> List[BaremetalFlavor]: + print("\n=== LIST K8S FLAVORS ===") + flavors = await client.cloud.k8s.flavors.list(exclude_gpu=True, include_capacity=True) + + display_count = min(3, len(flavors.results)) + for i in range(display_count): + flavor = flavors.results[i] + print( + f" {i + 1}. Flavor: ID={flavor.flavor_id}, name={flavor.flavor_name}, " + f"RAM={flavor.ram} MB, VCPUs={flavor.vcpus}" + ) + + if len(flavors.results) > display_count: + print(f" ... and {len(flavors.results) - display_count} more flavors") + + print(f"Total k8s flavors: {len(flavors.results)}") + print("========================") + return flavors.results + + +async def list_versions(*, client: AsyncGcore) -> List[K8SClusterVersion]: + print("\n=== LIST K8S VERSIONS ===") + versions = await client.cloud.k8s.list_versions() + for count, version in enumerate(versions.results, 1): + print(f" {count}. Version: {version.version}") + print(f"Total k8s versions: {len(versions.results)}") + print("=========================") + return versions.results + + +def _pick_pool_flavor(flavors: List[BaremetalFlavor]) -> str: + # Prefer the smallest g1-standard VM flavor with capacity; skip disabled, + # zero-capacity, and test/fake flavors. + candidates = [ + f + for f in flavors + if not f.disabled + and (f.capacity or 0) > 0 + and "test" not in f.flavor_name + and "fake" not in f.flavor_name + and f.flavor_name.startswith("g1-standard-") + ] + if not candidates: + raise ValueError("No suitable g1-standard k8s flavor with capacity found") + + pick = min(candidates, key=lambda f: f.ram) + print(f"Selected k8s pool flavor: {pick.flavor_name} (RAM: {pick.ram} MB, VCPUs: {pick.vcpus})") + return pick.flavor_id + + +def _pick_create_version(versions: List[K8SClusterVersion]) -> str: + if not versions: + raise ValueError("No k8s versions available for cluster creation") + # The API returns versions in ascending order. Pick the penultimate + # version when possible so the example always has a newer version to + # upgrade to later. Fall back to the only available version otherwise. + idx = max(len(versions) - 2, 0) + picked = versions[idx].version + print(f"Selected k8s version for creation: {picked}") + return picked + + +async def _get_first_pool(*, client: AsyncGcore, cluster_name: str) -> str: + pools = await client.cloud.k8s.clusters.pools.list(cluster_name=cluster_name) + if not pools.results: + raise ValueError(f"Cluster {cluster_name} has no pools") + return pools.results[0].name + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/snippets/invite_user_assign_cloud_role_issue_token.py b/examples/snippets/invite_user_assign_cloud_role_issue_token.py index c2c44c14..c7b5f7b3 100644 --- a/examples/snippets/invite_user_assign_cloud_role_issue_token.py +++ b/examples/snippets/invite_user_assign_cloud_role_issue_token.py @@ -46,7 +46,7 @@ def main() -> None: # Step 3: Create API token for the invited user (expires in 1 month) expires_in_days = 30 exp_date = (datetime.now(timezone.utc) + timedelta(days=expires_in_days)).isoformat() - gcore.iam.api_tokens.create( + gcore.iam.api_tokens.create( # pyright: ignore[reportDeprecated] client_id=client_id, client_user=ClientUser(role=user_role), exp_date=exp_date, diff --git a/pyproject.toml b/pyproject.toml index 5002951b..3118811d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "gcore" -version = "0.42.0" +version = "0.43.0" description = "The official Python library for the gcore API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/scripts/mock b/scripts/mock index 439a2c67..dd23ae18 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.20.2 -- steady --version + npm exec --package=@stdy/cli@0.22.1 -- steady --version - npm exec --package=@stdy/cli@0.20.2 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=dots --validator-form-object-format=dots "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=dots --validator-form-object-format=dots "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -48,5 +48,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.20.2 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=dots --validator-form-object-format=dots "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=dots --validator-form-object-format=dots "$URL" fi diff --git a/scripts/test b/scripts/test index f0d896d8..a2b72c7b 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! steady_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.20.2 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=dots --validator-form-object-format=dots${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=repeat --validator-form-array-format=repeat --validator-query-object-format=dots --validator-form-object-format=dots${NC}" echo exit 1 diff --git a/src/gcore/_files.py b/src/gcore/_files.py index cc14c14f..0fdce17b 100644 --- a/src/gcore/_files.py +++ b/src/gcore/_files.py @@ -3,8 +3,8 @@ import io import os import pathlib -from typing import overload -from typing_extensions import TypeGuard +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard import anyio @@ -17,7 +17,9 @@ HttpxFileContent, HttpxRequestFiles, ) -from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: @@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent: return await anyio.Path(file).read_bytes() return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/gcore/_utils/__init__.py b/src/gcore/_utils/__init__.py index 10cb66d2..1c090e51 100644 --- a/src/gcore/_utils/__init__.py +++ b/src/gcore/_utils/__init__.py @@ -24,7 +24,6 @@ coerce_integer as coerce_integer, file_from_path as file_from_path, strip_not_given as strip_not_given, - deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, maybe_coerce_float as maybe_coerce_float, get_required_header as get_required_header, diff --git a/src/gcore/_utils/_utils.py b/src/gcore/_utils/_utils.py index 63b8cd60..771859f5 100644 --- a/src/gcore/_utils/_utils.py +++ b/src/gcore/_utils/_utils.py @@ -177,21 +177,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: return isinstance(obj, Iterable) -def deepcopy_minimal(item: _T) -> _T: - """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: - - - mappings, e.g. `dict` - - list - - This is done for performance reasons. - """ - if is_mapping(item): - return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) - if is_list(item): - return cast(_T, [deepcopy_minimal(entry) for entry in item]) - return item - - # copied from https://github.com/Rapptz/RoboDanny def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: size = len(seq) diff --git a/src/gcore/_version.py b/src/gcore/_version.py index b88cfcf0..8f9eb568 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.42.0" # x-release-please-version +__version__ = "0.43.0" # x-release-please-version diff --git a/src/gcore/resources/cloud/k8s/clusters/clusters.py b/src/gcore/resources/cloud/k8s/clusters/clusters.py index d7951329..0c17df06 100644 --- a/src/gcore/resources/cloud/k8s/clusters/clusters.py +++ b/src/gcore/resources/cloud/k8s/clusters/clusters.py @@ -708,6 +708,239 @@ def upgrade( cast_to=TaskIDList, ) + def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + keypair: str, + name: str, + pools: Iterable[cluster_create_params.Pool], + version: str, + add_ons: cluster_create_params.AddOns | Omit = omit, + authentication: Optional[cluster_create_params.Authentication] | Omit = omit, + autoscaler_config: Optional[Dict[str, str]] | Omit = omit, + cni: Optional[cluster_create_params.Cni] | Omit = omit, + csi: cluster_create_params.Csi | Omit = omit, + ddos_profile: Optional[cluster_create_params.DDOSProfile] | Omit = omit, + fixed_network: Optional[str] | Omit = omit, + fixed_subnet: Optional[str] | Omit = omit, + is_ipv6: Optional[bool] | Omit = omit, + logging: Optional[cluster_create_params.Logging] | Omit = omit, + pods_ip_pool: Optional[str] | Omit = omit, + pods_ipv6_pool: Optional[str] | Omit = omit, + services_ip_pool: Optional[str] | Omit = omit, + services_ipv6_pool: Optional[str] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SCluster: + """ + Create a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = self.create( + project_id=project_id, + region_id=region_id, + keypair=keypair, + name=name, + pools=pools, + version=version, + add_ons=add_ons, + authentication=authentication, + autoscaler_config=autoscaler_config, + cni=cni, + csi=csi, + ddos_profile=ddos_profile, + fixed_network=fixed_network, + fixed_subnet=fixed_subnet, + is_ipv6=is_ipv6, + logging=logging, + pods_ip_pool=pods_ip_pool, + pods_ipv6_pool=pods_ipv6_pool, + services_ip_pool=services_ip_pool, + services_ipv6_pool=services_ipv6_pool, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) != 1: + raise ValueError("Expected exactly one task to be created") + task = self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + if not task.created_resources or not task.created_resources.k8s_clusters: + raise ValueError("No k8s cluster was created") + # K8s cluster `get` is keyed on cluster name (not UUID), and the name is + # unique per region, so reuse the name passed to `create`. + return self.get( + cluster_name=name, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + def update_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + add_ons: cluster_update_params.AddOns | Omit = omit, + authentication: Optional[cluster_update_params.Authentication] | Omit = omit, + autoscaler_config: Optional[Dict[str, str]] | Omit = omit, + cni: Optional[cluster_update_params.Cni] | Omit = omit, + ddos_profile: Optional[cluster_update_params.DDOSProfile] | Omit = omit, + logging: Optional[cluster_update_params.Logging] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SCluster: + """ + Update a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = self.update( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + add_ons=add_ons, + authentication=authentication, + autoscaler_config=autoscaler_config, + cni=cni, + ddos_profile=ddos_profile, + logging=logging, + 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( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return self.get( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + def upgrade_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + version: str, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SCluster: + """ + Upgrade a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = self.upgrade( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + version=version, + 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( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return self.get( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + def delete_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + volumes: str | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = self.delete( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + volumes=volumes, + 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( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + class AsyncClustersResource(AsyncAPIResource): """ @@ -1361,6 +1594,239 @@ async def upgrade( cast_to=TaskIDList, ) + async def create_and_poll( + self, + *, + project_id: int | None = None, + region_id: int | None = None, + keypair: str, + name: str, + pools: Iterable[cluster_create_params.Pool], + version: str, + add_ons: cluster_create_params.AddOns | Omit = omit, + authentication: Optional[cluster_create_params.Authentication] | Omit = omit, + autoscaler_config: Optional[Dict[str, str]] | Omit = omit, + cni: Optional[cluster_create_params.Cni] | Omit = omit, + csi: cluster_create_params.Csi | Omit = omit, + ddos_profile: Optional[cluster_create_params.DDOSProfile] | Omit = omit, + fixed_network: Optional[str] | Omit = omit, + fixed_subnet: Optional[str] | Omit = omit, + is_ipv6: Optional[bool] | Omit = omit, + logging: Optional[cluster_create_params.Logging] | Omit = omit, + pods_ip_pool: Optional[str] | Omit = omit, + pods_ipv6_pool: Optional[str] | Omit = omit, + services_ip_pool: Optional[str] | Omit = omit, + services_ipv6_pool: Optional[str] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SCluster: + """ + Create a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.create( + project_id=project_id, + region_id=region_id, + keypair=keypair, + name=name, + pools=pools, + version=version, + add_ons=add_ons, + authentication=authentication, + autoscaler_config=autoscaler_config, + cni=cni, + csi=csi, + ddos_profile=ddos_profile, + fixed_network=fixed_network, + fixed_subnet=fixed_subnet, + is_ipv6=is_ipv6, + logging=logging, + pods_ip_pool=pods_ip_pool, + pods_ipv6_pool=pods_ipv6_pool, + services_ip_pool=services_ip_pool, + services_ipv6_pool=services_ipv6_pool, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) != 1: + raise ValueError("Expected exactly one task to be created") + task = await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + if not task.created_resources or not task.created_resources.k8s_clusters: + raise ValueError("No k8s cluster was created") + # K8s cluster `get` is keyed on cluster name (not UUID), and the name is + # unique per region, so reuse the name passed to `create`. + return await self.get( + cluster_name=name, + 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_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + add_ons: cluster_update_params.AddOns | Omit = omit, + authentication: Optional[cluster_update_params.Authentication] | Omit = omit, + autoscaler_config: Optional[Dict[str, str]] | Omit = omit, + cni: Optional[cluster_update_params.Cni] | Omit = omit, + ddos_profile: Optional[cluster_update_params.DDOSProfile] | Omit = omit, + logging: Optional[cluster_update_params.Logging] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SCluster: + """ + Update a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.update( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + add_ons=add_ons, + authentication=authentication, + autoscaler_config=autoscaler_config, + cni=cni, + ddos_profile=ddos_profile, + logging=logging, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return await self.get( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + async def upgrade_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + version: str, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SCluster: + """ + Upgrade a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.upgrade( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + version=version, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return await self.get( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + async def delete_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + volumes: str | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a k8s cluster and poll for the result. Only the first task will be polled. If you need to poll more tasks, use the `tasks.poll` method. + """ + response = await self.delete( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + volumes=volumes, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + class ClustersResourceWithRawResponse: def __init__(self, clusters: ClustersResource) -> None: @@ -1390,6 +1856,18 @@ def __init__(self, clusters: ClustersResource) -> None: self.upgrade = to_raw_response_wrapper( clusters.upgrade, ) + self.create_and_poll = to_raw_response_wrapper( + clusters.create_and_poll, + ) + self.update_and_poll = to_raw_response_wrapper( + clusters.update_and_poll, + ) + self.upgrade_and_poll = to_raw_response_wrapper( + clusters.upgrade_and_poll, + ) + self.delete_and_poll = to_raw_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def kubeconfig(self) -> KubeconfigResourceWithRawResponse: @@ -1435,6 +1913,18 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.upgrade = async_to_raw_response_wrapper( clusters.upgrade, ) + self.create_and_poll = async_to_raw_response_wrapper( + clusters.create_and_poll, + ) + self.update_and_poll = async_to_raw_response_wrapper( + clusters.update_and_poll, + ) + self.upgrade_and_poll = async_to_raw_response_wrapper( + clusters.upgrade_and_poll, + ) + self.delete_and_poll = async_to_raw_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def kubeconfig(self) -> AsyncKubeconfigResourceWithRawResponse: @@ -1480,6 +1970,18 @@ def __init__(self, clusters: ClustersResource) -> None: self.upgrade = to_streamed_response_wrapper( clusters.upgrade, ) + self.create_and_poll = to_streamed_response_wrapper( + clusters.create_and_poll, + ) + self.update_and_poll = to_streamed_response_wrapper( + clusters.update_and_poll, + ) + self.upgrade_and_poll = to_streamed_response_wrapper( + clusters.upgrade_and_poll, + ) + self.delete_and_poll = to_streamed_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def kubeconfig(self) -> KubeconfigResourceWithStreamingResponse: @@ -1525,6 +2027,18 @@ def __init__(self, clusters: AsyncClustersResource) -> None: self.upgrade = async_to_streamed_response_wrapper( clusters.upgrade, ) + self.create_and_poll = async_to_streamed_response_wrapper( + clusters.create_and_poll, + ) + self.update_and_poll = async_to_streamed_response_wrapper( + clusters.update_and_poll, + ) + self.upgrade_and_poll = async_to_streamed_response_wrapper( + clusters.upgrade_and_poll, + ) + self.delete_and_poll = async_to_streamed_response_wrapper( + clusters.delete_and_poll, + ) @cached_property def kubeconfig(self) -> AsyncKubeconfigResourceWithStreamingResponse: diff --git a/src/gcore/resources/cloud/k8s/clusters/pools/pools.py b/src/gcore/resources/cloud/k8s/clusters/pools/pools.py index b261fe97..ddd8966b 100644 --- a/src/gcore/resources/cloud/k8s/clusters/pools/pools.py +++ b/src/gcore/resources/cloud/k8s/clusters/pools/pools.py @@ -560,6 +560,177 @@ def resize( cast_to=TaskIDList, ) + def create_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + flavor_id: str, + min_node_count: int, + name: str, + auto_healing_enabled: Optional[bool] | Omit = omit, + boot_volume_size: Optional[int] | Omit = omit, + boot_volume_type: Optional[Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"]] + | Omit = omit, + crio_config: Optional[Dict[str, str]] | Omit = omit, + is_public_ipv4: Optional[bool] | Omit = omit, + kubelet_config: Optional[Dict[str, str]] | Omit = omit, + labels: Optional[Dict[str, str]] | Omit = omit, + max_node_count: Optional[int] | Omit = omit, + servergroup_policy: Optional[Literal["affinity", "anti-affinity", "soft-anti-affinity"]] | Omit = omit, + taints: Optional[Dict[str, str]] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SClusterPool: + """ + Create a k8s cluster pool 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.create( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + flavor_id=flavor_id, + min_node_count=min_node_count, + name=name, + auto_healing_enabled=auto_healing_enabled, + boot_volume_size=boot_volume_size, + boot_volume_type=boot_volume_type, + crio_config=crio_config, + is_public_ipv4=is_public_ipv4, + kubelet_config=kubelet_config, + labels=labels, + max_node_count=max_node_count, + servergroup_policy=servergroup_policy, + taints=taints, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) != 1: + raise ValueError("Expected exactly one task to be created") + task = self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + if not task.created_resources or not task.created_resources.k8s_pools: + raise ValueError("No k8s cluster pool was created") + # K8s pool `get` is keyed on pool name (not UUID), and the name is + # unique within a cluster, so reuse the name passed to `create`. + return self.get( + pool_name=name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + def resize_and_poll( + self, + pool_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_name: str, + node_count: int, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SClusterPool: + """ + Resize a k8s cluster pool 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( + pool_name=pool_name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + node_count=node_count, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return self.get( + pool_name=pool_name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + def delete_and_poll( + self, + pool_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_name: str, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a k8s cluster pool 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( + pool_name=pool_name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + 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( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + class AsyncPoolsResource(AsyncAPIResource): @cached_property @@ -1081,6 +1252,177 @@ async def resize( cast_to=TaskIDList, ) + async def create_and_poll( + self, + cluster_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + flavor_id: str, + min_node_count: int, + name: str, + auto_healing_enabled: Optional[bool] | Omit = omit, + boot_volume_size: Optional[int] | Omit = omit, + boot_volume_type: Optional[Literal["cold", "ssd_hiiops", "ssd_local", "ssd_lowlatency", "standard", "ultra"]] + | Omit = omit, + crio_config: Optional[Dict[str, str]] | Omit = omit, + is_public_ipv4: Optional[bool] | Omit = omit, + kubelet_config: Optional[Dict[str, str]] | Omit = omit, + labels: Optional[Dict[str, str]] | Omit = omit, + max_node_count: Optional[int] | Omit = omit, + servergroup_policy: Optional[Literal["affinity", "anti-affinity", "soft-anti-affinity"]] | Omit = omit, + taints: Optional[Dict[str, str]] | Omit = omit, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SClusterPool: + """ + Create a k8s cluster pool 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.create( + cluster_name=cluster_name, + project_id=project_id, + region_id=region_id, + flavor_id=flavor_id, + min_node_count=min_node_count, + name=name, + auto_healing_enabled=auto_healing_enabled, + boot_volume_size=boot_volume_size, + boot_volume_type=boot_volume_type, + crio_config=crio_config, + is_public_ipv4=is_public_ipv4, + kubelet_config=kubelet_config, + labels=labels, + max_node_count=max_node_count, + servergroup_policy=servergroup_policy, + taints=taints, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks or len(response.tasks) != 1: + raise ValueError("Expected exactly one task to be created") + task = await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + if not task.created_resources or not task.created_resources.k8s_pools: + raise ValueError("No k8s cluster pool was created") + # K8s pool `get` is keyed on pool name (not UUID), and the name is + # unique within a cluster, so reuse the name passed to `create`. + return await self.get( + pool_name=name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + async def resize_and_poll( + self, + pool_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_name: str, + node_count: int, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> K8SClusterPool: + """ + Resize a k8s cluster pool 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( + pool_name=pool_name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + node_count=node_count, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + return await self.get( + pool_name=pool_name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + + async def delete_and_poll( + self, + pool_name: str, + *, + project_id: int | None = None, + region_id: int | None = None, + cluster_name: str, + polling_interval_seconds: int | Omit = omit, + polling_timeout_seconds: int | Omit = omit, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> None: + """ + Delete a k8s cluster pool 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( + pool_name=pool_name, + project_id=project_id, + region_id=region_id, + cluster_name=cluster_name, + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + ) + if not response.tasks: + raise ValueError("Expected at least one task to be created") + await self._client.cloud.tasks.poll( + response.tasks[0], + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + polling_interval_seconds=polling_interval_seconds, + polling_timeout_seconds=polling_timeout_seconds, + ) + class PoolsResourceWithRawResponse: def __init__(self, pools: PoolsResource) -> None: @@ -1107,6 +1449,15 @@ def __init__(self, pools: PoolsResource) -> None: self.resize = to_raw_response_wrapper( pools.resize, ) + self.create_and_poll = to_raw_response_wrapper( + pools.create_and_poll, + ) + self.resize_and_poll = to_raw_response_wrapper( + pools.resize_and_poll, + ) + self.delete_and_poll = to_raw_response_wrapper( + pools.delete_and_poll, + ) @cached_property def nodes(self) -> NodesResourceWithRawResponse: @@ -1138,6 +1489,15 @@ def __init__(self, pools: AsyncPoolsResource) -> None: self.resize = async_to_raw_response_wrapper( pools.resize, ) + self.create_and_poll = async_to_raw_response_wrapper( + pools.create_and_poll, + ) + self.resize_and_poll = async_to_raw_response_wrapper( + pools.resize_and_poll, + ) + self.delete_and_poll = async_to_raw_response_wrapper( + pools.delete_and_poll, + ) @cached_property def nodes(self) -> AsyncNodesResourceWithRawResponse: @@ -1169,6 +1529,15 @@ def __init__(self, pools: PoolsResource) -> None: self.resize = to_streamed_response_wrapper( pools.resize, ) + self.create_and_poll = to_streamed_response_wrapper( + pools.create_and_poll, + ) + self.resize_and_poll = to_streamed_response_wrapper( + pools.resize_and_poll, + ) + self.delete_and_poll = to_streamed_response_wrapper( + pools.delete_and_poll, + ) @cached_property def nodes(self) -> NodesResourceWithStreamingResponse: @@ -1200,6 +1569,15 @@ def __init__(self, pools: AsyncPoolsResource) -> None: self.resize = async_to_streamed_response_wrapper( pools.resize, ) + self.create_and_poll = async_to_streamed_response_wrapper( + pools.create_and_poll, + ) + self.resize_and_poll = async_to_streamed_response_wrapper( + pools.resize_and_poll, + ) + self.delete_and_poll = async_to_streamed_response_wrapper( + pools.delete_and_poll, + ) @cached_property def nodes(self) -> AsyncNodesResourceWithStreamingResponse: diff --git a/src/gcore/resources/iam/api_tokens.py b/src/gcore/resources/iam/api_tokens.py index 2ff21397..84369eb9 100644 --- a/src/gcore/resources/iam/api_tokens.py +++ b/src/gcore/resources/iam/api_tokens.py @@ -2,6 +2,7 @@ from __future__ import annotations +import typing_extensions from typing import Optional import httpx @@ -31,9 +32,14 @@ class APITokensResource(SyncAPIResource): You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -60,6 +66,7 @@ def with_streaming_response(self) -> APITokensResourceWithStreamingResponse: """ return APITokensResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") def create( self, client_id: int, @@ -75,7 +82,11 @@ def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APITokenCreated: - """ + """**Deprecated:** This endpoint will be removed on **2026-07-17**. + + Use + [`POST /v2/clients/{clientId}/tokens`](#operation/iamCreateApiTokenV2) instead. + Create an API token in the current account. Args: @@ -113,6 +124,7 @@ def create( cast_to=APITokenCreated, ) + @typing_extensions.deprecated("deprecated") def list( self, client_id: int, @@ -128,9 +140,12 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APITokenList: - """Get information about your permanent API tokens in the account. + """**Deprecated:** This endpoint will be removed on **2026-07-17**. - A user with the + Use + [`GET /v2/clients/{clientId}/tokens`](#operation/iamGetApiTokensV2) instead. + + Get information about your permanent API tokens in the account. A user with the Administrators role gets information about all API tokens in the account. Args: @@ -183,6 +198,7 @@ def list( cast_to=APITokenList, ) + @typing_extensions.deprecated("deprecated") def delete( self, token_id: int, @@ -195,9 +211,13 @@ def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """Delete API token from current account. + """**Deprecated:** This endpoint will be removed on **2026-07-17**. + + Use + [`DELETE /v2/clients/{clientId}/tokens/{tokenId}`](#operation/iamDeleteApiTokenV2) + instead. - Ensure that the API token is not being + Delete API token from current account. Ensure that the API token is not being used by an active application. After deleting the token, all applications that use this token will not be able to get access to your account via API. The action cannot be reversed. @@ -220,6 +240,7 @@ def delete( cast_to=NoneType, ) + @typing_extensions.deprecated("deprecated") def get( self, token_id: int, @@ -232,8 +253,11 @@ def get( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APIToken: - """ - Get API Token + """**Deprecated:** This endpoint will be removed on **2026-07-17**. + + Use + [`GET /v2/clients/{clientId}/tokens/{tokenId}`](#operation/iamGetApiTokenV2) + instead. Args: extra_headers: Send extra headers @@ -259,9 +283,14 @@ class AsyncAPITokensResource(AsyncAPIResource): You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -288,6 +317,7 @@ def with_streaming_response(self) -> AsyncAPITokensResourceWithStreamingResponse """ return AsyncAPITokensResourceWithStreamingResponse(self) + @typing_extensions.deprecated("deprecated") async def create( self, client_id: int, @@ -303,7 +333,11 @@ async def create( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APITokenCreated: - """ + """**Deprecated:** This endpoint will be removed on **2026-07-17**. + + Use + [`POST /v2/clients/{clientId}/tokens`](#operation/iamCreateApiTokenV2) instead. + Create an API token in the current account. Args: @@ -341,6 +375,7 @@ async def create( cast_to=APITokenCreated, ) + @typing_extensions.deprecated("deprecated") async def list( self, client_id: int, @@ -356,9 +391,12 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APITokenList: - """Get information about your permanent API tokens in the account. + """**Deprecated:** This endpoint will be removed on **2026-07-17**. - A user with the + Use + [`GET /v2/clients/{clientId}/tokens`](#operation/iamGetApiTokensV2) instead. + + Get information about your permanent API tokens in the account. A user with the Administrators role gets information about all API tokens in the account. Args: @@ -411,6 +449,7 @@ async def list( cast_to=APITokenList, ) + @typing_extensions.deprecated("deprecated") async def delete( self, token_id: int, @@ -423,9 +462,13 @@ async def delete( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> None: - """Delete API token from current account. + """**Deprecated:** This endpoint will be removed on **2026-07-17**. + + Use + [`DELETE /v2/clients/{clientId}/tokens/{tokenId}`](#operation/iamDeleteApiTokenV2) + instead. - Ensure that the API token is not being + Delete API token from current account. Ensure that the API token is not being used by an active application. After deleting the token, all applications that use this token will not be able to get access to your account via API. The action cannot be reversed. @@ -448,6 +491,7 @@ async def delete( cast_to=NoneType, ) + @typing_extensions.deprecated("deprecated") async def get( self, token_id: int, @@ -460,8 +504,11 @@ async def get( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> APIToken: - """ - Get API Token + """**Deprecated:** This endpoint will be removed on **2026-07-17**. + + Use + [`GET /v2/clients/{clientId}/tokens/{tokenId}`](#operation/iamGetApiTokenV2) + instead. Args: extra_headers: Send extra headers @@ -485,17 +532,25 @@ class APITokensResourceWithRawResponse: def __init__(self, api_tokens: APITokensResource) -> None: self._api_tokens = api_tokens - self.create = to_raw_response_wrapper( - api_tokens.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + api_tokens.create, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_raw_response_wrapper( - api_tokens.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + api_tokens.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_raw_response_wrapper( - api_tokens.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + api_tokens.delete, # pyright: ignore[reportDeprecated], + ) ) - self.get = to_raw_response_wrapper( - api_tokens.get, + self.get = ( # pyright: ignore[reportDeprecated] + to_raw_response_wrapper( + api_tokens.get, # pyright: ignore[reportDeprecated], + ) ) @@ -503,17 +558,25 @@ class AsyncAPITokensResourceWithRawResponse: def __init__(self, api_tokens: AsyncAPITokensResource) -> None: self._api_tokens = api_tokens - self.create = async_to_raw_response_wrapper( - api_tokens.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + api_tokens.create, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_raw_response_wrapper( - api_tokens.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + api_tokens.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_raw_response_wrapper( - api_tokens.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + api_tokens.delete, # pyright: ignore[reportDeprecated], + ) ) - self.get = async_to_raw_response_wrapper( - api_tokens.get, + self.get = ( # pyright: ignore[reportDeprecated] + async_to_raw_response_wrapper( + api_tokens.get, # pyright: ignore[reportDeprecated], + ) ) @@ -521,17 +584,25 @@ class APITokensResourceWithStreamingResponse: def __init__(self, api_tokens: APITokensResource) -> None: self._api_tokens = api_tokens - self.create = to_streamed_response_wrapper( - api_tokens.create, + self.create = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + api_tokens.create, # pyright: ignore[reportDeprecated], + ) ) - self.list = to_streamed_response_wrapper( - api_tokens.list, + self.list = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + api_tokens.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = to_streamed_response_wrapper( - api_tokens.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + api_tokens.delete, # pyright: ignore[reportDeprecated], + ) ) - self.get = to_streamed_response_wrapper( - api_tokens.get, + self.get = ( # pyright: ignore[reportDeprecated] + to_streamed_response_wrapper( + api_tokens.get, # pyright: ignore[reportDeprecated], + ) ) @@ -539,15 +610,23 @@ class AsyncAPITokensResourceWithStreamingResponse: def __init__(self, api_tokens: AsyncAPITokensResource) -> None: self._api_tokens = api_tokens - self.create = async_to_streamed_response_wrapper( - api_tokens.create, + self.create = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + api_tokens.create, # pyright: ignore[reportDeprecated], + ) ) - self.list = async_to_streamed_response_wrapper( - api_tokens.list, + self.list = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + api_tokens.list, # pyright: ignore[reportDeprecated], + ) ) - self.delete = async_to_streamed_response_wrapper( - api_tokens.delete, + self.delete = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + api_tokens.delete, # pyright: ignore[reportDeprecated], + ) ) - self.get = async_to_streamed_response_wrapper( - api_tokens.get, + self.get = ( # pyright: ignore[reportDeprecated] + async_to_streamed_response_wrapper( + api_tokens.get, # pyright: ignore[reportDeprecated], + ) ) diff --git a/src/gcore/resources/iam/iam.py b/src/gcore/resources/iam/iam.py index ff1ef03f..e8eea114 100644 --- a/src/gcore/resources/iam/iam.py +++ b/src/gcore/resources/iam/iam.py @@ -47,9 +47,14 @@ def api_tokens(self) -> APITokensResource: You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -113,9 +118,14 @@ def api_tokens(self) -> AsyncAPITokensResource: You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -182,9 +192,14 @@ def api_tokens(self) -> APITokensResourceWithRawResponse: You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -213,9 +228,14 @@ def api_tokens(self) -> AsyncAPITokensResourceWithRawResponse: You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -244,9 +264,14 @@ def api_tokens(self) -> APITokensResourceWithStreamingResponse: You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. @@ -275,9 +300,14 @@ def api_tokens(self) -> AsyncAPITokensResourceWithStreamingResponse: You can either set its validity period when creating it or issue a token for an unlimited time. Please address the API documentation of the specific product in order to check if it supports API tokens. + Newer endpoints under `/v2/…` issue tokens using `_` (underscore) as the separator + (for example `42_a1b2c3d4e5f6...`) and are the recommended way to create new tokens. + Legacy endpoints that issue `$`-separated tokens are marked deprecated and will be removed + on **2026-07-17**; tokens that were already issued keep authenticating. + Provide your APIKey in the Authorization header. - Example: ```curl -H "Authorization: APIKey 123$61b8e1e7a68c" https://api.gcore.com/iam/users/me``` + Example: ```curl -H "Authorization: APIKey 42_a1b2c3d4e5f6..." https://api.gcore.com/iam/users/me``` Please note: When authorizing via SAML SSO, our system does not have any information about permissions given to the user by the identity provider. diff --git a/src/gcore/resources/storage/locations.py b/src/gcore/resources/storage/locations.py index 34fb96cf..ae1321f9 100644 --- a/src/gcore/resources/storage/locations.py +++ b/src/gcore/resources/storage/locations.py @@ -23,6 +23,8 @@ class LocationsResource(SyncAPIResource): + """Locations represent cloud regions where new storages can be created.""" + @cached_property def with_raw_response(self) -> LocationsResourceWithRawResponse: """ @@ -95,6 +97,8 @@ def list( class AsyncLocationsResource(AsyncAPIResource): + """Locations represent cloud regions where new storages can be created.""" + @cached_property def with_raw_response(self) -> AsyncLocationsResourceWithRawResponse: """ diff --git a/src/gcore/resources/storage/storage.py b/src/gcore/resources/storage/storage.py index 4252f3f9..b81116c0 100644 --- a/src/gcore/resources/storage/storage.py +++ b/src/gcore/resources/storage/storage.py @@ -51,6 +51,7 @@ class StorageResource(SyncAPIResource): @cached_property def locations(self) -> LocationsResource: + """Locations represent cloud regions where new storages can be created.""" return LocationsResource(self._client) @cached_property @@ -92,6 +93,7 @@ def with_streaming_response(self) -> StorageResourceWithStreamingResponse: class AsyncStorageResource(AsyncAPIResource): @cached_property def locations(self) -> AsyncLocationsResource: + """Locations represent cloud regions where new storages can be created.""" return AsyncLocationsResource(self._client) @cached_property @@ -136,6 +138,7 @@ def __init__(self, storage: StorageResource) -> None: @cached_property def locations(self) -> LocationsResourceWithRawResponse: + """Locations represent cloud regions where new storages can be created.""" return LocationsResourceWithRawResponse(self._storage.locations) @cached_property @@ -161,6 +164,7 @@ def __init__(self, storage: AsyncStorageResource) -> None: @cached_property def locations(self) -> AsyncLocationsResourceWithRawResponse: + """Locations represent cloud regions where new storages can be created.""" return AsyncLocationsResourceWithRawResponse(self._storage.locations) @cached_property @@ -186,6 +190,7 @@ def __init__(self, storage: StorageResource) -> None: @cached_property def locations(self) -> LocationsResourceWithStreamingResponse: + """Locations represent cloud regions where new storages can be created.""" return LocationsResourceWithStreamingResponse(self._storage.locations) @cached_property @@ -211,6 +216,7 @@ def __init__(self, storage: AsyncStorageResource) -> None: @cached_property def locations(self) -> AsyncLocationsResourceWithStreamingResponse: + """Locations represent cloud regions where new storages can be created.""" return AsyncLocationsResourceWithStreamingResponse(self._storage.locations) @cached_property diff --git a/src/gcore/types/cdn/cdn_resource.py b/src/gcore/types/cdn/cdn_resource.py index 72cd4ffe..f56336ea 100644 --- a/src/gcore/types/cdn/cdn_resource.py +++ b/src/gcore/types/cdn/cdn_resource.py @@ -11,8 +11,6 @@ "CDNResource", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -50,7 +48,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -86,35 +83,6 @@ class OptionsAllowedHTTPMethods(BaseModel): value: List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]] -class OptionsBotProtectionBotChallenge(BaseModel): - """Controls the bot challenge module state.""" - - enabled: Optional[bool] = None - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(BaseModel): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: OptionsBotProtectionBotChallenge - """Controls the bot challenge module state.""" - - enabled: bool - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(BaseModel): """Compresses content with Brotli on the CDN side. @@ -1158,38 +1126,6 @@ class OptionsReferrerACL(BaseModel): """ -class OptionsRequestLimiter(BaseModel): - """Option allows to limit the amount of HTTP requests.""" - - enabled: bool - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: int - """Maximum request rate.""" - - burst: Optional[int] = None - - delay: Optional[int] = None - - rate_unit: Optional[Literal["r/s", "r/m"]] = None - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(BaseModel): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1683,12 +1619,6 @@ class Options(BaseModel): allowed_http_methods: Optional[OptionsAllowedHTTPMethods] = FieldInfo(alias="allowedHttpMethods", default=None) """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] = None - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] = None """Compresses content with Brotli on the CDN side. @@ -1937,9 +1867,6 @@ class Options(BaseModel): referrer_acl: Optional[OptionsReferrerACL] = None """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] = None - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] = None """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resource_create_params.py b/src/gcore/types/cdn/cdn_resource_create_params.py index b4937197..4179df9b 100644 --- a/src/gcore/types/cdn/cdn_resource_create_params.py +++ b/src/gcore/types/cdn/cdn_resource_create_params.py @@ -12,8 +12,6 @@ "CDNResourceCreateParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -51,7 +49,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -209,35 +206,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1283,34 +1251,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1806,12 +1746,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -2060,9 +1994,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resource_replace_params.py b/src/gcore/types/cdn/cdn_resource_replace_params.py index 3b2ccbe4..fa2b8f7d 100644 --- a/src/gcore/types/cdn/cdn_resource_replace_params.py +++ b/src/gcore/types/cdn/cdn_resource_replace_params.py @@ -12,8 +12,6 @@ "CDNResourceReplaceParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -51,7 +49,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -182,35 +179,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1256,34 +1224,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1779,12 +1719,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -2033,9 +1967,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resource_update_params.py b/src/gcore/types/cdn/cdn_resource_update_params.py index 8471bee1..80204d02 100644 --- a/src/gcore/types/cdn/cdn_resource_update_params.py +++ b/src/gcore/types/cdn/cdn_resource_update_params.py @@ -12,8 +12,6 @@ "CDNResourceUpdateParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -51,7 +49,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -173,35 +170,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1247,34 +1215,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1770,12 +1710,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -2024,9 +1958,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resources/cdn_resource_rule.py b/src/gcore/types/cdn/cdn_resources/cdn_resource_rule.py index 6c3b02bf..04c0e43a 100644 --- a/src/gcore/types/cdn/cdn_resources/cdn_resource_rule.py +++ b/src/gcore/types/cdn/cdn_resources/cdn_resource_rule.py @@ -11,8 +11,6 @@ "CDNResourceRule", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -48,7 +46,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -80,35 +77,6 @@ class OptionsAllowedHTTPMethods(BaseModel): value: List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]] -class OptionsBotProtectionBotChallenge(BaseModel): - """Controls the bot challenge module state.""" - - enabled: Optional[bool] = None - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(BaseModel): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: OptionsBotProtectionBotChallenge - """Controls the bot challenge module state.""" - - enabled: bool - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(BaseModel): """Compresses content with Brotli on the CDN side. @@ -1109,38 +1077,6 @@ class OptionsReferrerACL(BaseModel): """ -class OptionsRequestLimiter(BaseModel): - """Option allows to limit the amount of HTTP requests.""" - - enabled: bool - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: int - """Maximum request rate.""" - - burst: Optional[int] = None - - delay: Optional[int] = None - - rate_unit: Optional[Literal["r/s", "r/m"]] = None - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(BaseModel): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1544,12 +1480,6 @@ class Options(BaseModel): allowed_http_methods: Optional[OptionsAllowedHTTPMethods] = FieldInfo(alias="allowedHttpMethods", default=None) """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] = None - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] = None """Compresses content with Brotli on the CDN side. @@ -1789,9 +1719,6 @@ class Options(BaseModel): referrer_acl: Optional[OptionsReferrerACL] = None """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] = None - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] = None """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resources/rule_create_params.py b/src/gcore/types/cdn/cdn_resources/rule_create_params.py index 0426cd93..23231c88 100644 --- a/src/gcore/types/cdn/cdn_resources/rule_create_params.py +++ b/src/gcore/types/cdn/cdn_resources/rule_create_params.py @@ -12,8 +12,6 @@ "RuleCreateParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -49,7 +47,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -154,35 +151,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1185,34 +1153,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1618,12 +1558,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -1863,9 +1797,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resources/rule_replace_params.py b/src/gcore/types/cdn/cdn_resources/rule_replace_params.py index 4cbdefaf..712ba111 100644 --- a/src/gcore/types/cdn/cdn_resources/rule_replace_params.py +++ b/src/gcore/types/cdn/cdn_resources/rule_replace_params.py @@ -12,8 +12,6 @@ "RuleReplaceParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -49,7 +47,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -156,35 +153,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1187,34 +1155,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1620,12 +1560,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -1865,9 +1799,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/cdn_resources/rule_update_params.py b/src/gcore/types/cdn/cdn_resources/rule_update_params.py index b98453ba..a1949296 100644 --- a/src/gcore/types/cdn/cdn_resources/rule_update_params.py +++ b/src/gcore/types/cdn/cdn_resources/rule_update_params.py @@ -12,8 +12,6 @@ "RuleUpdateParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -49,7 +47,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -156,35 +153,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1187,34 +1155,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1620,12 +1560,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -1865,9 +1799,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/rule_template.py b/src/gcore/types/cdn/rule_template.py index a2eb3725..dad63715 100644 --- a/src/gcore/types/cdn/rule_template.py +++ b/src/gcore/types/cdn/rule_template.py @@ -11,8 +11,6 @@ "RuleTemplate", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -48,7 +46,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -80,35 +77,6 @@ class OptionsAllowedHTTPMethods(BaseModel): value: List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]] -class OptionsBotProtectionBotChallenge(BaseModel): - """Controls the bot challenge module state.""" - - enabled: Optional[bool] = None - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(BaseModel): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: OptionsBotProtectionBotChallenge - """Controls the bot challenge module state.""" - - enabled: bool - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(BaseModel): """Compresses content with Brotli on the CDN side. @@ -1109,38 +1077,6 @@ class OptionsReferrerACL(BaseModel): """ -class OptionsRequestLimiter(BaseModel): - """Option allows to limit the amount of HTTP requests.""" - - enabled: bool - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: int - """Maximum request rate.""" - - burst: Optional[int] = None - - delay: Optional[int] = None - - rate_unit: Optional[Literal["r/s", "r/m"]] = None - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(BaseModel): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1544,12 +1480,6 @@ class Options(BaseModel): allowed_http_methods: Optional[OptionsAllowedHTTPMethods] = FieldInfo(alias="allowedHttpMethods", default=None) """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] = None - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] = None """Compresses content with Brotli on the CDN side. @@ -1789,9 +1719,6 @@ class Options(BaseModel): referrer_acl: Optional[OptionsReferrerACL] = None """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] = None - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] = None """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/rule_template_create_params.py b/src/gcore/types/cdn/rule_template_create_params.py index 1a4d8a41..86ffee85 100644 --- a/src/gcore/types/cdn/rule_template_create_params.py +++ b/src/gcore/types/cdn/rule_template_create_params.py @@ -12,8 +12,6 @@ "RuleTemplateCreateParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -49,7 +47,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -138,35 +135,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1169,34 +1137,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1602,12 +1542,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -1847,9 +1781,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/rule_template_replace_params.py b/src/gcore/types/cdn/rule_template_replace_params.py index 2fe52fdc..1d838bab 100644 --- a/src/gcore/types/cdn/rule_template_replace_params.py +++ b/src/gcore/types/cdn/rule_template_replace_params.py @@ -12,8 +12,6 @@ "RuleTemplateReplaceParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -49,7 +47,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -138,35 +135,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1169,34 +1137,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1602,12 +1542,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -1847,9 +1781,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/src/gcore/types/cdn/rule_template_update_params.py b/src/gcore/types/cdn/rule_template_update_params.py index cccc6bc0..5892b4f2 100644 --- a/src/gcore/types/cdn/rule_template_update_params.py +++ b/src/gcore/types/cdn/rule_template_update_params.py @@ -12,8 +12,6 @@ "RuleTemplateUpdateParams", "Options", "OptionsAllowedHTTPMethods", - "OptionsBotProtection", - "OptionsBotProtectionBotChallenge", "OptionsBrotliCompression", "OptionsBrowserCacheSettings", "OptionsCacheHTTPHeaders", @@ -49,7 +47,6 @@ "OptionsRedirectHTTPToHTTPS", "OptionsRedirectHTTPSToHTTP", "OptionsReferrerACL", - "OptionsRequestLimiter", "OptionsResponseHeadersHidingPolicy", "OptionsRewrite", "OptionsSecureKey", @@ -138,35 +135,6 @@ class OptionsAllowedHTTPMethods(TypedDict, total=False): value: Required[List[Literal["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]]] -class OptionsBotProtectionBotChallenge(TypedDict, total=False): - """Controls the bot challenge module state.""" - - enabled: bool - """Possible values: - - - **true** - Bot challenge is enabled. - - **false** - Bot challenge is disabled. - """ - - -class OptionsBotProtection(TypedDict, total=False): - """ - Allows to prevent online services from overloading and ensure your business workflow running smoothly. - """ - - bot_challenge: Required[OptionsBotProtectionBotChallenge] - """Controls the bot challenge module state.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - class OptionsBrotliCompression(TypedDict, total=False): """Compresses content with Brotli on the CDN side. @@ -1169,34 +1137,6 @@ class OptionsReferrerACL(TypedDict, total=False): """ -class OptionsRequestLimiter(TypedDict, total=False): - """Option allows to limit the amount of HTTP requests.""" - - enabled: Required[bool] - """Controls the option state. - - Possible values: - - - **true** - Option is enabled. - - **false** - Option is disabled. - """ - - rate: Required[int] - """Maximum request rate.""" - - rate_unit: Literal["r/s", "r/m"] - """Units of measurement for the `rate` field. - - Possible values: - - - **r/s** - Requests per second. - - **r/m** - Requests per minute. - - If the rate is less than one request per second, it is specified in request per - minute (r/m.) - """ - - class OptionsResponseHeadersHidingPolicy(TypedDict, total=False): """Hides HTTP headers from an origin server in the CDN response.""" @@ -1602,12 +1542,6 @@ class Options(TypedDict, total=False): allowed_http_methods: Annotated[Optional[OptionsAllowedHTTPMethods], PropertyInfo(alias="allowedHttpMethods")] """HTTP methods allowed for content requests from the CDN.""" - bot_protection: Optional[OptionsBotProtection] - """ - Allows to prevent online services from overloading and ensure your business - workflow running smoothly. - """ - brotli_compression: Optional[OptionsBrotliCompression] """Compresses content with Brotli on the CDN side. @@ -1847,9 +1781,6 @@ class Options(TypedDict, total=False): referrer_acl: Optional[OptionsReferrerACL] """Controls access to the CDN resource content for specified domain names.""" - request_limiter: Optional[OptionsRequestLimiter] - """Option allows to limit the amount of HTTP requests.""" - response_headers_hiding_policy: Optional[OptionsResponseHeadersHidingPolicy] """Hides HTTP headers from an origin server in the CDN response.""" diff --git a/tests/api_resources/cdn/cdn_resources/test_rules.py b/tests/api_resources/cdn/cdn_resources/test_rules.py index e7a410e0..f7af8461 100644 --- a/tests/api_resources/cdn/cdn_resources/test_rules.py +++ b/tests/api_resources/cdn/cdn_resources/test_rules.py @@ -43,10 +43,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -227,11 +223,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -359,10 +350,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -543,11 +530,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -785,10 +767,6 @@ def test_method_replace_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -969,11 +947,6 @@ def test_method_replace_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1110,10 +1083,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1294,11 +1263,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1426,10 +1390,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1610,11 +1570,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1852,10 +1807,6 @@ async def test_method_replace_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -2036,11 +1987,6 @@ async def test_method_replace_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], diff --git a/tests/api_resources/cdn/test_cdn_resources.py b/tests/api_resources/cdn/test_cdn_resources.py index 5ac9ab6a..7b6238da 100644 --- a/tests/api_resources/cdn/test_cdn_resources.py +++ b/tests/api_resources/cdn/test_cdn_resources.py @@ -39,10 +39,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -231,11 +227,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -382,10 +373,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -574,11 +561,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1017,10 +999,6 @@ def test_method_replace_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1209,11 +1187,6 @@ def test_method_replace_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1363,10 +1336,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1555,11 +1524,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1706,10 +1670,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1898,11 +1858,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -2341,10 +2296,6 @@ async def test_method_replace_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -2533,11 +2484,6 @@ async def test_method_replace_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], diff --git a/tests/api_resources/cdn/test_rule_templates.py b/tests/api_resources/cdn/test_rule_templates.py index b81bbb02..03fa0906 100644 --- a/tests/api_resources/cdn/test_rule_templates.py +++ b/tests/api_resources/cdn/test_rule_templates.py @@ -39,10 +39,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -223,11 +219,6 @@ def test_method_create_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -347,10 +338,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -531,11 +518,6 @@ def test_method_update_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -754,10 +736,6 @@ def test_method_replace_with_all_params(self, client: Gcore) -> None: "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -938,11 +916,6 @@ def test_method_replace_with_all_params(self, client: Gcore) -> None: "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1072,10 +1045,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1256,11 +1225,6 @@ async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1380,10 +1344,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1564,11 +1524,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], @@ -1787,10 +1742,6 @@ async def test_method_replace_with_all_params(self, async_client: AsyncGcore) -> "enabled": True, "value": ["GET", "POST"], }, - "bot_protection": { - "bot_challenge": {"enabled": True}, - "enabled": True, - }, "brotli_compression": { "enabled": True, "value": ["text/html", "text/plain"], @@ -1971,11 +1922,6 @@ async def test_method_replace_with_all_params(self, async_client: AsyncGcore) -> "excepted_values": ["example.com", "*.example.net"], "policy_type": "deny", }, - "request_limiter": { - "enabled": True, - "rate": 5, - "rate_unit": "r/s", - }, "response_headers_hiding_policy": { "enabled": True, "excepted": ["my-header"], diff --git a/tests/api_resources/iam/test_api_tokens.py b/tests/api_resources/iam/test_api_tokens.py index 314e9993..fa911d10 100644 --- a/tests/api_resources/iam/test_api_tokens.py +++ b/tests/api_resources/iam/test_api_tokens.py @@ -11,6 +11,8 @@ from tests.utils import assert_matches_type from gcore.types.iam import APIToken, APITokenList, APITokenCreated +# pyright: reportDeprecated=false + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -19,38 +21,43 @@ class TestAPITokens: @parametrize def test_method_create(self, client: Gcore) -> None: - api_token = client.iam.api_tokens.create( - client_id=0, - client_user={}, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - ) + with pytest.warns(DeprecationWarning): + api_token = client.iam.api_tokens.create( + client_id=0, + client_user={}, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + ) + assert_matches_type(APITokenCreated, api_token, path=["response"]) @parametrize def test_method_create_with_all_params(self, client: Gcore) -> None: - api_token = client.iam.api_tokens.create( - client_id=0, - client_user={ - "role": { - "id": 1, - "name": "Administrators", - } - }, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - description="It's my token", - ) + with pytest.warns(DeprecationWarning): + api_token = client.iam.api_tokens.create( + client_id=0, + client_user={ + "role": { + "id": 1, + "name": "Administrators", + } + }, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + description="It's my token", + ) + assert_matches_type(APITokenCreated, api_token, path=["response"]) @parametrize def test_raw_response_create(self, client: Gcore) -> None: - response = client.iam.api_tokens.with_raw_response.create( - client_id=0, - client_user={}, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - ) + with pytest.warns(DeprecationWarning): + response = client.iam.api_tokens.with_raw_response.create( + client_id=0, + client_user={}, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -59,43 +66,49 @@ def test_raw_response_create(self, client: Gcore) -> None: @parametrize def test_streaming_response_create(self, client: Gcore) -> None: - with client.iam.api_tokens.with_streaming_response.create( - client_id=0, - client_user={}, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - api_token = response.parse() - assert_matches_type(APITokenCreated, api_token, path=["response"]) + with pytest.warns(DeprecationWarning): + with client.iam.api_tokens.with_streaming_response.create( + client_id=0, + client_user={}, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_token = response.parse() + assert_matches_type(APITokenCreated, api_token, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_method_list(self, client: Gcore) -> None: - api_token = client.iam.api_tokens.list( - client_id=0, - ) + with pytest.warns(DeprecationWarning): + api_token = client.iam.api_tokens.list( + client_id=0, + ) + assert_matches_type(APITokenList, api_token, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Gcore) -> None: - api_token = client.iam.api_tokens.list( - client_id=0, - deleted=True, - issued_by=0, - not_issued_by=0, - role="role", - ) + with pytest.warns(DeprecationWarning): + api_token = client.iam.api_tokens.list( + client_id=0, + deleted=True, + issued_by=0, + not_issued_by=0, + role="role", + ) + assert_matches_type(APITokenList, api_token, path=["response"]) @parametrize def test_raw_response_list(self, client: Gcore) -> None: - response = client.iam.api_tokens.with_raw_response.list( - client_id=0, - ) + with pytest.warns(DeprecationWarning): + response = client.iam.api_tokens.with_raw_response.list( + client_id=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -104,31 +117,35 @@ def test_raw_response_list(self, client: Gcore) -> None: @parametrize def test_streaming_response_list(self, client: Gcore) -> None: - with client.iam.api_tokens.with_streaming_response.list( - client_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.iam.api_tokens.with_streaming_response.list( + client_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - api_token = response.parse() - assert_matches_type(APITokenList, api_token, path=["response"]) + api_token = response.parse() + assert_matches_type(APITokenList, api_token, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize def test_method_delete(self, client: Gcore) -> None: - api_token = client.iam.api_tokens.delete( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + api_token = client.iam.api_tokens.delete( + token_id=0, + client_id=0, + ) + assert api_token is None @parametrize def test_raw_response_delete(self, client: Gcore) -> None: - response = client.iam.api_tokens.with_raw_response.delete( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + response = client.iam.api_tokens.with_raw_response.delete( + token_id=0, + client_id=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -137,32 +154,36 @@ def test_raw_response_delete(self, client: Gcore) -> None: @parametrize def test_streaming_response_delete(self, client: Gcore) -> None: - with client.iam.api_tokens.with_streaming_response.delete( - token_id=0, - client_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.iam.api_tokens.with_streaming_response.delete( + token_id=0, + client_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - api_token = response.parse() - assert api_token is None + api_token = response.parse() + assert api_token is None assert cast(Any, response.is_closed) is True @parametrize def test_method_get(self, client: Gcore) -> None: - api_token = client.iam.api_tokens.get( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + api_token = client.iam.api_tokens.get( + token_id=0, + client_id=0, + ) + assert_matches_type(APIToken, api_token, path=["response"]) @parametrize def test_raw_response_get(self, client: Gcore) -> None: - response = client.iam.api_tokens.with_raw_response.get( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + response = client.iam.api_tokens.with_raw_response.get( + token_id=0, + client_id=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -171,15 +192,16 @@ def test_raw_response_get(self, client: Gcore) -> None: @parametrize def test_streaming_response_get(self, client: Gcore) -> None: - with client.iam.api_tokens.with_streaming_response.get( - token_id=0, - client_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + with client.iam.api_tokens.with_streaming_response.get( + token_id=0, + client_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - api_token = response.parse() - assert_matches_type(APIToken, api_token, path=["response"]) + api_token = response.parse() + assert_matches_type(APIToken, api_token, path=["response"]) assert cast(Any, response.is_closed) is True @@ -191,38 +213,43 @@ class TestAsyncAPITokens: @parametrize async def test_method_create(self, async_client: AsyncGcore) -> None: - api_token = await async_client.iam.api_tokens.create( - client_id=0, - client_user={}, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - ) + with pytest.warns(DeprecationWarning): + api_token = await async_client.iam.api_tokens.create( + client_id=0, + client_user={}, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + ) + assert_matches_type(APITokenCreated, api_token, path=["response"]) @parametrize async def test_method_create_with_all_params(self, async_client: AsyncGcore) -> None: - api_token = await async_client.iam.api_tokens.create( - client_id=0, - client_user={ - "role": { - "id": 1, - "name": "Administrators", - } - }, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - description="It's my token", - ) + with pytest.warns(DeprecationWarning): + api_token = await async_client.iam.api_tokens.create( + client_id=0, + client_user={ + "role": { + "id": 1, + "name": "Administrators", + } + }, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + description="It's my token", + ) + assert_matches_type(APITokenCreated, api_token, path=["response"]) @parametrize async def test_raw_response_create(self, async_client: AsyncGcore) -> None: - response = await async_client.iam.api_tokens.with_raw_response.create( - client_id=0, - client_user={}, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - ) + with pytest.warns(DeprecationWarning): + response = await async_client.iam.api_tokens.with_raw_response.create( + client_id=0, + client_user={}, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -231,43 +258,49 @@ async def test_raw_response_create(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_create(self, async_client: AsyncGcore) -> None: - async with async_client.iam.api_tokens.with_streaming_response.create( - client_id=0, - client_user={}, - exp_date="2021-01-01T12:00:00.000000Z", - name="My token", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - api_token = await response.parse() - assert_matches_type(APITokenCreated, api_token, path=["response"]) + with pytest.warns(DeprecationWarning): + async with async_client.iam.api_tokens.with_streaming_response.create( + client_id=0, + client_user={}, + exp_date="2021-01-01T12:00:00.000000Z", + name="My token", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_token = await response.parse() + assert_matches_type(APITokenCreated, api_token, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_method_list(self, async_client: AsyncGcore) -> None: - api_token = await async_client.iam.api_tokens.list( - client_id=0, - ) + with pytest.warns(DeprecationWarning): + api_token = await async_client.iam.api_tokens.list( + client_id=0, + ) + assert_matches_type(APITokenList, api_token, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncGcore) -> None: - api_token = await async_client.iam.api_tokens.list( - client_id=0, - deleted=True, - issued_by=0, - not_issued_by=0, - role="role", - ) + with pytest.warns(DeprecationWarning): + api_token = await async_client.iam.api_tokens.list( + client_id=0, + deleted=True, + issued_by=0, + not_issued_by=0, + role="role", + ) + assert_matches_type(APITokenList, api_token, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncGcore) -> None: - response = await async_client.iam.api_tokens.with_raw_response.list( - client_id=0, - ) + with pytest.warns(DeprecationWarning): + response = await async_client.iam.api_tokens.with_raw_response.list( + client_id=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -276,31 +309,35 @@ async def test_raw_response_list(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_list(self, async_client: AsyncGcore) -> None: - async with async_client.iam.api_tokens.with_streaming_response.list( - client_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.iam.api_tokens.with_streaming_response.list( + client_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - api_token = await response.parse() - assert_matches_type(APITokenList, api_token, path=["response"]) + api_token = await response.parse() + assert_matches_type(APITokenList, api_token, path=["response"]) assert cast(Any, response.is_closed) is True @parametrize async def test_method_delete(self, async_client: AsyncGcore) -> None: - api_token = await async_client.iam.api_tokens.delete( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + api_token = await async_client.iam.api_tokens.delete( + token_id=0, + client_id=0, + ) + assert api_token is None @parametrize async def test_raw_response_delete(self, async_client: AsyncGcore) -> None: - response = await async_client.iam.api_tokens.with_raw_response.delete( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + response = await async_client.iam.api_tokens.with_raw_response.delete( + token_id=0, + client_id=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -309,32 +346,36 @@ async def test_raw_response_delete(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_delete(self, async_client: AsyncGcore) -> None: - async with async_client.iam.api_tokens.with_streaming_response.delete( - token_id=0, - client_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" + with pytest.warns(DeprecationWarning): + async with async_client.iam.api_tokens.with_streaming_response.delete( + token_id=0, + client_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" - api_token = await response.parse() - assert api_token is None + api_token = await response.parse() + assert api_token is None assert cast(Any, response.is_closed) is True @parametrize async def test_method_get(self, async_client: AsyncGcore) -> None: - api_token = await async_client.iam.api_tokens.get( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + api_token = await async_client.iam.api_tokens.get( + token_id=0, + client_id=0, + ) + assert_matches_type(APIToken, api_token, path=["response"]) @parametrize async def test_raw_response_get(self, async_client: AsyncGcore) -> None: - response = await async_client.iam.api_tokens.with_raw_response.get( - token_id=0, - client_id=0, - ) + with pytest.warns(DeprecationWarning): + response = await async_client.iam.api_tokens.with_raw_response.get( + token_id=0, + client_id=0, + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -343,14 +384,15 @@ async def test_raw_response_get(self, async_client: AsyncGcore) -> None: @parametrize async def test_streaming_response_get(self, async_client: AsyncGcore) -> None: - async with async_client.iam.api_tokens.with_streaming_response.get( - token_id=0, - client_id=0, - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - api_token = await response.parse() - assert_matches_type(APIToken, api_token, path=["response"]) + with pytest.warns(DeprecationWarning): + async with async_client.iam.api_tokens.with_streaming_response.get( + token_id=0, + client_id=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + api_token = await response.parse() + assert_matches_type(APIToken, api_token, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py deleted file mode 100644 index fe51174e..00000000 --- a/tests/test_deepcopy.py +++ /dev/null @@ -1,58 +0,0 @@ -from gcore._utils import deepcopy_minimal - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert id(obj1) != id(obj2) - - -def test_simple_dict() -> None: - obj1 = {"foo": "bar"} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_dict() -> None: - obj1 = {"foo": {"bar": True}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - - -def test_complex_nested_dict() -> None: - obj1 = {"foo": {"bar": [{"hello": "world"}]}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) - assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) - - -def test_simple_list() -> None: - obj1 = ["a", "b", "c"] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_list() -> None: - obj1 = ["a", [1, 2, 3]] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1[1], obj2[1]) - - -class MyObject: ... - - -def test_ignores_other_types() -> None: - # custom classes - my_obj = MyObject() - obj1 = {"foo": my_obj} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert obj1["foo"] is my_obj - - # tuples - obj3 = ("a", "b") - obj4 = deepcopy_minimal(obj3) - assert obj3 is obj4 diff --git a/tests/test_files.py b/tests/test_files.py index d8e5a6f7..054b0adf 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,7 +4,8 @@ import pytest from dirty_equals import IsDict, IsList, IsBytes, IsTuple -from gcore._files import to_httpx_files, async_to_httpx_files +from gcore._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files +from gcore._utils import extract_files readme_path = Path(__file__).parent.parent.joinpath("README.md") @@ -49,3 +50,99 @@ def test_string_not_allowed() -> None: "file": "foo", # type: ignore } ) + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert obj1 is not obj2 + + +class TestDeepcopyWithPaths: + def test_copies_top_level_dict(self) -> None: + original = {"file": b"data", "other": "value"} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + + def test_file_value_is_same_reference(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + assert result["file"] is file_bytes + + def test_list_popped_wholesale(self) -> None: + files = [b"f1", b"f2"] + original = {"files": files, "title": "t"} + result = deepcopy_with_paths(original, [["files", ""]]) + assert_different_identities(result, original) + result_files = result["files"] + assert isinstance(result_files, list) + assert_different_identities(result_files, files) + + def test_nested_array_path_copies_list_and_elements(self) -> None: + elem1 = {"file": b"f1", "extra": 1} + elem2 = {"file": b"f2", "extra": 2} + original = {"items": [elem1, elem2]} + result = deepcopy_with_paths(original, [["items", "", "file"]]) + assert_different_identities(result, original) + result_items = result["items"] + assert isinstance(result_items, list) + assert_different_identities(result_items, original["items"]) + assert_different_identities(result_items[0], elem1) + assert_different_identities(result_items[1], elem2) + + def test_empty_paths_returns_same_object(self) -> None: + original = {"foo": "bar"} + result = deepcopy_with_paths(original, []) + assert result is original + + def test_multiple_paths(self) -> None: + f1 = b"file1" + f2 = b"file2" + original = {"a": f1, "b": f2, "c": "unchanged"} + result = deepcopy_with_paths(original, [["a"], ["b"]]) + assert_different_identities(result, original) + assert result["a"] is f1 + assert result["b"] is f2 + assert result["c"] is original["c"] + + def test_extract_files_does_not_mutate_original_top_level(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes, "other": "value"} + + copied = deepcopy_with_paths(original, [["file"]]) + extracted = extract_files(copied, paths=[["file"]]) + + assert extracted == [("file", file_bytes)] + assert original == {"file": file_bytes, "other": "value"} + assert copied == {"other": "value"} + + def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: + file1 = b"f1" + file2 = b"f2" + original = { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + + copied = deepcopy_with_paths(original, [["items", "", "file"]]) + extracted = extract_files(copied, paths=[["items", "", "file"]]) + + assert extracted == [("items[][file]", file1), ("items[][file]", file2)] + assert original == { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + assert copied == { + "items": [ + {"extra": 1}, + {"extra": 2}, + ], + "title": "example", + }