Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5db7792
fix: use correct field name format for multipart file arrays
stainless-app[bot] Apr 27, 2026
c5bc635
fix(cloud)!: correct pg conf validation response model name
stainless-app[bot] Apr 27, 2026
342223e
test(iam): unskip IMP-1903 tests and refine IMP-1904 skip reason
pedrodeoliveira Apr 27, 2026
48f6c89
feat: support setting headers via env
stainless-app[bot] Apr 27, 2026
ea5b262
codegen metadata
stainless-app[bot] Apr 28, 2026
8dcda97
test(iam): update the skip reason on the IAM user-list tests
pedrodeoliveira Apr 28, 2026
ed94e72
codegen metadata
stainless-app[bot] Apr 28, 2026
fad3629
feat(iam): migrate api_tokens to v2 endpoints
stainless-app[bot] Apr 28, 2026
0fd7ea5
codegen metadata
stainless-app[bot] Apr 29, 2026
b7a6c07
feat(storage): add get method for access keys
stainless-app[bot] Apr 29, 2026
680e40d
feat(api): aggregated API specs update
stainless-app[bot] Apr 29, 2026
3cb482c
codegen metadata
stainless-app[bot] Apr 29, 2026
8985183
feat(api): aggregated API specs update
stainless-app[bot] Apr 30, 2026
377df41
feat(api): aggregated API specs update
stainless-app[bot] Apr 30, 2026
98209fa
feat(api): aggregated API specs update
stainless-app[bot] Apr 30, 2026
87a7eb2
codegen metadata
stainless-app[bot] Apr 30, 2026
4701fe6
chore(internal): reformat pyproject.toml
stainless-app[bot] Apr 30, 2026
ee5d831
feat(api): aggregated API specs update
stainless-app[bot] May 4, 2026
9678a5d
codegen metadata
stainless-app[bot] May 4, 2026
1c3322d
fix(cloud): match hw_machine_type literal in instances images poll me…
pedrodeoliveira May 4, 2026
a4499c7
feat(cloud)!: switch routers update from v1 to v2
stainless-app[bot] May 4, 2026
6a3523a
feat(cloud): add update_and_poll method for routers
pedrodeoliveira May 4, 2026
f3e9aa6
feat(cloud): use routers update_and_poll in network examples
pedrodeoliveira May 4, 2026
aa51d9b
release: 0.45.0
stainless-app[bot] May 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.44.0"
".": "0.45.0"
}
8 changes: 4 additions & 4 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 657
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore%2Fgcore-8c3bd3792724737d30da0f0200f0c589f9942ea3a028fcc9bcd56f3490ffc29a.yml
openapi_spec_hash: 7b184f4e52554b89cadf11b43f395583
config_hash: aa5a9dd05b6324fcb454d0694e5901a3
configured_endpoints: 658
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gcore/gcore-63b63d8be16530f04e4c07443b8f3ed2554e65dd6343d1ac13a48f89c2cf9fc4.yml
openapi_spec_hash: 6f72f97fd9545f3ecda586de840c68ee
config_hash: 88e4af508ede520a45a0563d9cf077cc
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,40 @@
# Changelog

## 0.45.0 (2026-05-04)

Full Changelog: [v0.44.0...v0.45.0](https://github.com/G-Core/gcore-python/compare/v0.44.0...v0.45.0)

### ⚠ BREAKING CHANGES

* **cloud:** switch routers update from v1 to v2
* **cloud:** correct pg conf validation response model name

### Features

* **api:** aggregated API specs update ([ee5d831](https://github.com/G-Core/gcore-python/commit/ee5d831570b46fb37395a5972d7e29112a7e6442))
* **api:** aggregated API specs update ([98209fa](https://github.com/G-Core/gcore-python/commit/98209fad538c45d924d9ac35d058d22b3970069e))
* **api:** aggregated API specs update ([377df41](https://github.com/G-Core/gcore-python/commit/377df417b485d59c6c58f3ccbd5155fadd2067f6))
* **api:** aggregated API specs update ([8985183](https://github.com/G-Core/gcore-python/commit/898518310e07028a47f72a33f3524577f9e83376))
* **api:** aggregated API specs update ([680e40d](https://github.com/G-Core/gcore-python/commit/680e40d2e2bc16202d0c70164a7f7603a133cd9c))
* **cloud:** add update_and_poll method for routers ([6a3523a](https://github.com/G-Core/gcore-python/commit/6a3523a2eafdc436f176c5ea78f229331cc7f835))
* **cloud:** switch routers update from v1 to v2 ([a4499c7](https://github.com/G-Core/gcore-python/commit/a4499c76aa8bf0b19792134c68bdb24d02732c61))
* **cloud:** use routers update_and_poll in network examples ([f3e9aa6](https://github.com/G-Core/gcore-python/commit/f3e9aa6f969960316ac8282b2cb6875023bb17d8))
* **iam:** migrate api_tokens to v2 endpoints ([fad3629](https://github.com/G-Core/gcore-python/commit/fad362941ce456930120fb2f8c22413807597d78))
* **storage:** add get method for access keys ([b7a6c07](https://github.com/G-Core/gcore-python/commit/b7a6c074732285b2244eb012c6deef4bf39ad56e))
* support setting headers via env ([48f6c89](https://github.com/G-Core/gcore-python/commit/48f6c89a1755f00ee53c9764273e937864c59666))


### Bug Fixes

* **cloud:** correct pg conf validation response model name ([c5bc635](https://github.com/G-Core/gcore-python/commit/c5bc635407def6e004bdb94a24e9045a1f8cf416))
* **cloud:** match hw_machine_type literal in instances images poll methods ([1c3322d](https://github.com/G-Core/gcore-python/commit/1c3322d6535eb206742fce884d8ee9d51363c0dd))
* use correct field name format for multipart file arrays ([5db7792](https://github.com/G-Core/gcore-python/commit/5db779213e0c088d2a615dccd43e0f7ca94bea1a))


### Chores

* **internal:** reformat pyproject.toml ([4701fe6](https://github.com/G-Core/gcore-python/commit/4701fe6e238ea59aec4b3de804e882e249e496be))

## 0.44.0 (2026-04-27)

Full Changelog: [v0.43.0...v0.44.0](https://github.com/G-Core/gcore-python/compare/v0.43.0...v0.44.0)
Expand Down
2 changes: 1 addition & 1 deletion examples/cloud/networks.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ def get_router(*, client: Gcore, router_id: str) -> None:

def update_router(*, client: Gcore, router_id: str) -> None:
print("\n=== UPDATE ROUTER ===")
router = client.cloud.networks.routers.update(router_id=router_id, name="gcore-go-example-updated") # pyright: ignore[reportDeprecated]
router = client.cloud.networks.routers.update_and_poll(router_id=router_id, name="gcore-go-example-updated")
print(f"Updated router: ID={router.id}, name={router.name}")
print("========================")

Expand Down
2 changes: 1 addition & 1 deletion examples/cloud/networks_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ async def get_router(*, client: AsyncGcore, router_id: str) -> None:

async def update_router(*, client: AsyncGcore, router_id: str) -> None:
print("\n=== UPDATE ROUTER ===")
router = await client.cloud.networks.routers.update(router_id=router_id, name="gcore-go-example-updated") # pyright: ignore[reportDeprecated]
router = await client.cloud.networks.routers.update_and_poll(router_id=router_id, name="gcore-go-example-updated")
print(f"Updated router: ID={router.id}, name={router.name}")
print("========================")

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "gcore"
version = "0.44.0"
version = "0.45.0"
description = "The official Python library for the gcore API"
dynamic = ["readme"]
license = "Apache-2.0"
Expand Down Expand Up @@ -170,7 +170,7 @@ show_error_codes = true
#
# We also exclude our `tests` as mypy doesn't always infer
# types correctly and Pyright will still catch any type errors.
exclude = ['src/gcore/_files.py', '_dev/.*.py', 'tests/.*']
exclude = ["src/gcore/_files.py", "_dev/.*.py", "tests/.*"]

strict_equality = true
implicit_reexport = true
Expand Down
25 changes: 24 additions & 1 deletion src/gcore/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
RequestOptions,
not_given,
)
from ._utils import is_given, get_async_library, maybe_coerce_integer
from ._utils import (
is_given,
is_mapping_t,
get_async_library,
maybe_coerce_integer,
)
from ._compat import cached_property
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
Expand Down Expand Up @@ -116,6 +121,15 @@ def __init__(
if base_url is None:
base_url = f"https://api.gcore.com"

custom_headers_env = os.environ.get("GCORE_CUSTOM_HEADERS")
if custom_headers_env is not None:
parsed: dict[str, str] = {}
for line in custom_headers_env.split("\n"):
colon = line.find(":")
if colon >= 0:
parsed[line[:colon].strip()] = line[colon + 1 :].strip()
default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

super().__init__(
version=__version__,
base_url=base_url,
Expand Down Expand Up @@ -395,6 +409,15 @@ def __init__(
if base_url is None:
base_url = f"https://api.gcore.com"

custom_headers_env = os.environ.get("GCORE_CUSTOM_HEADERS")
if custom_headers_env is not None:
parsed: dict[str, str] = {}
for line in custom_headers_env.split("\n"):
colon = line.find(":")
if colon >= 0:
parsed[line[:colon].strip()] = line[colon + 1 :].strip()
default_headers = {**parsed, **(default_headers if is_mapping_t(default_headers) else {})}

super().__init__(
version=__version__,
base_url=base_url,
Expand Down
8 changes: 2 additions & 6 deletions src/gcore/_qs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@

from typing import Any, List, Tuple, Union, Mapping, TypeVar
from urllib.parse import parse_qs, urlencode
from typing_extensions import Literal, get_args
from typing_extensions import get_args

from ._types import NotGiven, not_given
from ._types import NotGiven, ArrayFormat, NestedFormat, not_given
from ._utils import flatten

_T = TypeVar("_T")


ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
NestedFormat = Literal["dots", "brackets"]

PrimitiveData = Union[str, int, float, bool, None]
# this should be Data = Union[PrimitiveData, "List[Data]", "Tuple[Data]", "Mapping[str, Data]"]
# https://github.com/microsoft/pyright/issues/3555
Expand Down
3 changes: 3 additions & 0 deletions src/gcore/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
ModelT = TypeVar("ModelT", bound=pydantic.BaseModel)
_T = TypeVar("_T")

ArrayFormat = Literal["comma", "repeat", "indices", "brackets"]
NestedFormat = Literal["dots", "brackets"]


# Approximates httpx internal ProxiesTypes and RequestFiles types
# while adding support for `PathLike` instances
Expand Down
42 changes: 34 additions & 8 deletions src/gcore/_utils/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
)
from pathlib import Path
from datetime import date, datetime
from typing_extensions import TypeGuard
from typing_extensions import TypeGuard, get_args

import sniffio

from .._types import Omit, NotGiven, FileTypes, HeadersLike
from .._types import Omit, NotGiven, FileTypes, ArrayFormat, HeadersLike

_T = TypeVar("_T")
_TupleT = TypeVar("_TupleT", bound=Tuple[object, ...])
Expand All @@ -40,25 +40,45 @@ def extract_files(
query: Mapping[str, object],
*,
paths: Sequence[Sequence[str]],
array_format: ArrayFormat = "brackets",
) -> list[tuple[str, FileTypes]]:
"""Recursively extract files from the given dictionary based on specified paths.

A path may look like this ['foo', 'files', '<array>', 'data'].

``array_format`` controls how ``<array>`` segments contribute to the emitted
field name. Supported values: ``"brackets"`` (``foo[]``), ``"repeat"`` and
``"comma"`` (``foo``), ``"indices"`` (``foo[0]``, ``foo[1]``).

Note: this mutates the given dictionary.
"""
files: list[tuple[str, FileTypes]] = []
for path in paths:
files.extend(_extract_items(query, path, index=0, flattened_key=None))
files.extend(_extract_items(query, path, index=0, flattened_key=None, array_format=array_format))
return files


def _array_suffix(array_format: ArrayFormat, array_index: int) -> str:
if array_format == "brackets":
return "[]"
if array_format == "indices":
return f"[{array_index}]"
if array_format == "repeat" or array_format == "comma":
# Both repeat the bare field name for each file part; there is no
# meaningful way to comma-join binary parts.
return ""
raise NotImplementedError(
f"Unknown array_format value: {array_format}, choose from {', '.join(get_args(ArrayFormat))}"
)


def _extract_items(
obj: object,
path: Sequence[str],
*,
index: int,
flattened_key: str | None,
array_format: ArrayFormat,
) -> list[tuple[str, FileTypes]]:
try:
key = path[index]
Expand All @@ -75,9 +95,11 @@ def _extract_items(

if is_list(obj):
files: list[tuple[str, FileTypes]] = []
for entry in obj:
assert_is_file_content(entry, key=flattened_key + "[]" if flattened_key else "")
files.append((flattened_key + "[]", cast(FileTypes, entry)))
for array_index, entry in enumerate(obj):
suffix = _array_suffix(array_format, array_index)
emitted_key = (flattened_key + suffix) if flattened_key else suffix
assert_is_file_content(entry, key=emitted_key)
files.append((emitted_key, cast(FileTypes, entry)))
return files

assert_is_file_content(obj, key=flattened_key)
Expand Down Expand Up @@ -106,6 +128,7 @@ def _extract_items(
path,
index=index,
flattened_key=flattened_key,
array_format=array_format,
)
elif is_list(obj):
if key != "<array>":
Expand All @@ -117,9 +140,12 @@ def _extract_items(
item,
path,
index=index,
flattened_key=flattened_key + "[]" if flattened_key is not None else "[]",
flattened_key=(
(flattened_key if flattened_key is not None else "") + _array_suffix(array_format, array_index)
),
array_format=array_format,
)
for item in obj
for array_index, item in enumerate(obj)
]
)

Expand Down
2 changes: 1 addition & 1 deletion src/gcore/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

__title__ = "gcore"
__version__ = "0.44.0" # x-release-please-version
__version__ = "0.45.0" # x-release-please-version
6 changes: 3 additions & 3 deletions src/gcore/resources/cloud/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ from gcore.types.cloud.networks import Router, RouterList, SubnetID
Methods:

- <code title="post /cloud/v1/routers/{project_id}/{region_id}">client.cloud.networks.routers.<a href="./src/gcore/resources/cloud/networks/routers.py">create</a>(\*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/networks/router_create_params.py">params</a>) -> <a href="./src/gcore/types/cloud/task_id_list.py">TaskIDList</a></code>
- <code title="patch /cloud/v1/routers/{project_id}/{region_id}/{router_id}">client.cloud.networks.routers.<a href="./src/gcore/resources/cloud/networks/routers.py">update</a>(router_id, \*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/networks/router_update_params.py">params</a>) -> <a href="./src/gcore/types/cloud/networks/router.py">Router</a></code>
- <code title="patch /cloud/v2/routers/{project_id}/{region_id}/{router_id}">client.cloud.networks.routers.<a href="./src/gcore/resources/cloud/networks/routers.py">update</a>(router_id, \*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/networks/router_update_params.py">params</a>) -> <a href="./src/gcore/types/cloud/task_id_list.py">TaskIDList</a></code>
- <code title="get /cloud/v1/routers/{project_id}/{region_id}">client.cloud.networks.routers.<a href="./src/gcore/resources/cloud/networks/routers.py">list</a>(\*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/networks/router_list_params.py">params</a>) -> <a href="./src/gcore/types/cloud/networks/router.py">SyncOffsetPage[Router]</a></code>
- <code title="delete /cloud/v1/routers/{project_id}/{region_id}/{router_id}">client.cloud.networks.routers.<a href="./src/gcore/resources/cloud/networks/routers.py">delete</a>(router_id, \*, project_id, region_id) -> <a href="./src/gcore/types/cloud/task_id_list.py">TaskIDList</a></code>
- <code title="post /cloud/v1/routers/{project_id}/{region_id}/{router_id}/attach">client.cloud.networks.routers.<a href="./src/gcore/resources/cloud/networks/routers.py">attach_subnet</a>(router_id, \*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/networks/router_attach_subnet_params.py">params</a>) -> <a href="./src/gcore/types/cloud/networks/router.py">Router</a></code>
Expand Down Expand Up @@ -1190,12 +1190,12 @@ Methods:
Types:

```python
from gcore.types.cloud.databases.postgres import CustomConfigurationValidateResponse
from gcore.types.cloud.databases.postgres import PgConfValidation
```

Methods:

- <code title="post /cloud/v1/dbaas/postgres/validate_pg_conf/{project_id}/{region_id}">client.cloud.databases.postgres.custom_configurations.<a href="./src/gcore/resources/cloud/databases/postgres/custom_configurations.py">validate</a>(\*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/databases/postgres/custom_configuration_validate_params.py">params</a>) -> <a href="./src/gcore/types/cloud/databases/postgres/custom_configuration_validate_response.py">CustomConfigurationValidateResponse</a></code>
- <code title="post /cloud/v1/dbaas/postgres/validate_pg_conf/{project_id}/{region_id}">client.cloud.databases.postgres.custom_configurations.<a href="./src/gcore/resources/cloud/databases/postgres/custom_configurations.py">validate</a>(\*, project_id, region_id, \*\*<a href="src/gcore/types/cloud/databases/postgres/custom_configuration_validate_params.py">params</a>) -> <a href="./src/gcore/types/cloud/databases/postgres/pg_conf_validation.py">PgConfValidation</a></code>

## VolumeSnapshots

Expand Down
2 changes: 2 additions & 0 deletions src/gcore/resources/cloud/audit_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def list(
"rebuild",
"regenerate_credentials",
"remove_from_servergroup",
"replace",
"replace_metadata",
"resize",
"resume",
Expand Down Expand Up @@ -291,6 +292,7 @@ def list(
"rebuild",
"regenerate_credentials",
"remove_from_servergroup",
"replace",
"replace_metadata",
"resize",
"resume",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@
)
from ....._base_client import make_request_options
from .....types.cloud.databases.postgres import custom_configuration_validate_params
from .....types.cloud.databases.postgres.custom_configuration_validate_response import (
CustomConfigurationValidateResponse,
)
from .....types.cloud.databases.postgres.pg_conf_validation import PgConfValidation

__all__ = ["CustomConfigurationsResource", "AsyncCustomConfigurationsResource"]

Expand Down Expand Up @@ -56,7 +54,7 @@ def validate(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CustomConfigurationValidateResponse:
) -> PgConfValidation:
"""
Validate a custom PostgreSQL configuration file.

Expand Down Expand Up @@ -97,7 +95,7 @@ def validate(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=CustomConfigurationValidateResponse,
cast_to=PgConfValidation,
)


Expand Down Expand Up @@ -134,7 +132,7 @@ async def validate(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> CustomConfigurationValidateResponse:
) -> PgConfValidation:
"""
Validate a custom PostgreSQL configuration file.

Expand Down Expand Up @@ -175,7 +173,7 @@ async def validate(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
cast_to=CustomConfigurationValidateResponse,
cast_to=PgConfValidation,
)


Expand Down
Loading
Loading