Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@
{
".": "1.3.2"
".": "1.4.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: 106
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-63dab7833d6670810c4f4882df560ebbfe2de8e8e1a98d51422368607b5335ae.yml
openapi_spec_hash: ebb5068064f7469f9239b18a51a6fe44
config_hash: fd168de77f219e46a1427bbec2eecfb9
configured_endpoints: 111
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-84e997ca5716b9378a58a1bdf3d6616cf3be80156a6aaed1bed469fe93ba2c95.yml
openapi_spec_hash: b44a4ba1c2c3cb775c14545f2bab05a8
config_hash: 22f65246be4646c23dde9f69f51252e7
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# Changelog

## 1.4.0 (2026-01-30)

Full Changelog: [v1.3.2...v1.4.0](https://github.com/runloopai/api-client-python/compare/v1.3.2...v1.4.0)

### Features

* **client:** add custom JSON encoder for extended type support ([840addd](https://github.com/runloopai/api-client-python/commit/840addd8884fd93ede0c9bbc2a16cf399d6372d0))
* **devbox:** add gateway routes ([#7212](https://github.com/runloopai/api-client-python/issues/7212)) ([2527bb7](https://github.com/runloopai/api-client-python/commit/2527bb7714f9ce9114964286bfbb35582ceaa976))
* **devbox:** add new tunnel APIs and deprecate old tunnel API ([#7227](https://github.com/runloopai/api-client-python/issues/7227)) ([71620a1](https://github.com/runloopai/api-client-python/commit/71620a114080506898864b6d73378d4ff30b1070))


### Chores

* **devbox:** deprecate remove tunnel API ([#7230](https://github.com/runloopai/api-client-python/issues/7230)) ([d48832d](https://github.com/runloopai/api-client-python/commit/d48832d0aa076bd621decae3f5dadca7141c29d3))
* **documentation:** made warning message language more accurate ([#7215](https://github.com/runloopai/api-client-python/issues/7215)) ([08e0586](https://github.com/runloopai/api-client-python/commit/08e058660cf02e67701899e3b46173c5e9011b7d))

## 1.3.2 (2026-01-30)

Full Changelog: [v1.3.1...v1.3.2](https://github.com/runloopai/api-client-python/compare/v1.3.1...v1.3.2)
Expand Down
21 changes: 21 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,3 +397,24 @@ Methods:
- <code title="post /v1/network-policies/{id}">client.network_policies.<a href="./src/runloop_api_client/resources/network_policies.py">update</a>(id, \*\*<a href="src/runloop_api_client/types/network_policy_update_params.py">params</a>) -> <a href="./src/runloop_api_client/types/network_policy_view.py">NetworkPolicyView</a></code>
- <code title="get /v1/network-policies">client.network_policies.<a href="./src/runloop_api_client/resources/network_policies.py">list</a>(\*\*<a href="src/runloop_api_client/types/network_policy_list_params.py">params</a>) -> <a href="./src/runloop_api_client/types/network_policy_view.py">SyncNetworkPoliciesCursorIDPage[NetworkPolicyView]</a></code>
- <code title="post /v1/network-policies/{id}/delete">client.network_policies.<a href="./src/runloop_api_client/resources/network_policies.py">delete</a>(id) -> <a href="./src/runloop_api_client/types/network_policy_view.py">NetworkPolicyView</a></code>

# GatewayConfigs

Types:

```python
from runloop_api_client.types import (
GatewayConfigCreateParameters,
GatewayConfigListView,
GatewayConfigUpdateParameters,
GatewayConfigView,
)
```

Methods:

- <code title="post /v1/gateway-configs">client.gateway_configs.<a href="./src/runloop_api_client/resources/gateway_configs.py">create</a>(\*\*<a href="src/runloop_api_client/types/gateway_config_create_params.py">params</a>) -> <a href="./src/runloop_api_client/types/gateway_config_view.py">GatewayConfigView</a></code>
- <code title="get /v1/gateway-configs/{id}">client.gateway_configs.<a href="./src/runloop_api_client/resources/gateway_configs.py">retrieve</a>(id) -> <a href="./src/runloop_api_client/types/gateway_config_view.py">GatewayConfigView</a></code>
- <code title="post /v1/gateway-configs/{id}">client.gateway_configs.<a href="./src/runloop_api_client/resources/gateway_configs.py">update</a>(id, \*\*<a href="src/runloop_api_client/types/gateway_config_update_params.py">params</a>) -> <a href="./src/runloop_api_client/types/gateway_config_view.py">GatewayConfigView</a></code>
- <code title="get /v1/gateway-configs">client.gateway_configs.<a href="./src/runloop_api_client/resources/gateway_configs.py">list</a>(\*\*<a href="src/runloop_api_client/types/gateway_config_list_params.py">params</a>) -> <a href="./src/runloop_api_client/types/gateway_config_view.py">SyncGatewayConfigsCursorIDPage[GatewayConfigView]</a></code>
- <code title="post /v1/gateway-configs/{id}/delete">client.gateway_configs.<a href="./src/runloop_api_client/resources/gateway_configs.py">delete</a>(id) -> <a href="./src/runloop_api_client/types/gateway_config_view.py">GatewayConfigView</a></code>
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "runloop_api_client"
version = "1.3.2"
version = "1.4.0"
description = "The official Python library for the runloop API"
dynamic = ["readme"]
license = "MIT"
Expand Down
7 changes: 5 additions & 2 deletions src/runloop_api_client/_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
APIConnectionError,
APIResponseValidationError,
)
from ._utils._json import openapi_dumps

log: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -554,8 +555,10 @@ def _build_request(
kwargs["content"] = options.content
elif isinstance(json_data, bytes):
kwargs["content"] = json_data
else:
kwargs["json"] = json_data if is_given(json_data) else None
elif not files:
# Don't set content when JSON is sent as multipart/form-data,
# since httpx's content param overrides other body arguments
kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
Expand Down
38 changes: 38 additions & 0 deletions src/runloop_api_client/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
repositories,
benchmark_jobs,
benchmark_runs,
gateway_configs,
network_policies,
)
from .resources.agents import AgentsResource, AsyncAgentsResource
Expand All @@ -52,6 +53,7 @@
from .resources.repositories import RepositoriesResource, AsyncRepositoriesResource
from .resources.benchmark_jobs import BenchmarkJobsResource, AsyncBenchmarkJobsResource
from .resources.benchmark_runs import BenchmarkRunsResource, AsyncBenchmarkRunsResource
from .resources.gateway_configs import GatewayConfigsResource, AsyncGatewayConfigsResource
from .resources.network_policies import NetworkPoliciesResource, AsyncNetworkPoliciesResource
from .resources.devboxes.devboxes import DevboxesResource, AsyncDevboxesResource
from .resources.scenarios.scenarios import ScenariosResource, AsyncScenariosResource
Expand Down Expand Up @@ -182,6 +184,12 @@ def network_policies(self) -> NetworkPoliciesResource:

return NetworkPoliciesResource(self)

@cached_property
def gateway_configs(self) -> GatewayConfigsResource:
from .resources.gateway_configs import GatewayConfigsResource

return GatewayConfigsResource(self)

@cached_property
def with_raw_response(self) -> RunloopWithRawResponse:
return RunloopWithRawResponse(self)
Expand Down Expand Up @@ -418,6 +426,12 @@ def network_policies(self) -> AsyncNetworkPoliciesResource:

return AsyncNetworkPoliciesResource(self)

@cached_property
def gateway_configs(self) -> AsyncGatewayConfigsResource:
from .resources.gateway_configs import AsyncGatewayConfigsResource

return AsyncGatewayConfigsResource(self)

@cached_property
def with_raw_response(self) -> AsyncRunloopWithRawResponse:
return AsyncRunloopWithRawResponse(self)
Expand Down Expand Up @@ -603,6 +617,12 @@ def network_policies(self) -> network_policies.NetworkPoliciesResourceWithRawRes

return NetworkPoliciesResourceWithRawResponse(self._client.network_policies)

@cached_property
def gateway_configs(self) -> gateway_configs.GatewayConfigsResourceWithRawResponse:
from .resources.gateway_configs import GatewayConfigsResourceWithRawResponse

return GatewayConfigsResourceWithRawResponse(self._client.gateway_configs)


class AsyncRunloopWithRawResponse:
_client: AsyncRunloop
Expand Down Expand Up @@ -676,6 +696,12 @@ def network_policies(self) -> network_policies.AsyncNetworkPoliciesResourceWithR

return AsyncNetworkPoliciesResourceWithRawResponse(self._client.network_policies)

@cached_property
def gateway_configs(self) -> gateway_configs.AsyncGatewayConfigsResourceWithRawResponse:
from .resources.gateway_configs import AsyncGatewayConfigsResourceWithRawResponse

return AsyncGatewayConfigsResourceWithRawResponse(self._client.gateway_configs)


class RunloopWithStreamedResponse:
_client: Runloop
Expand Down Expand Up @@ -749,6 +775,12 @@ def network_policies(self) -> network_policies.NetworkPoliciesResourceWithStream

return NetworkPoliciesResourceWithStreamingResponse(self._client.network_policies)

@cached_property
def gateway_configs(self) -> gateway_configs.GatewayConfigsResourceWithStreamingResponse:
from .resources.gateway_configs import GatewayConfigsResourceWithStreamingResponse

return GatewayConfigsResourceWithStreamingResponse(self._client.gateway_configs)


class AsyncRunloopWithStreamedResponse:
_client: AsyncRunloop
Expand Down Expand Up @@ -822,6 +854,12 @@ def network_policies(self) -> network_policies.AsyncNetworkPoliciesResourceWithS

return AsyncNetworkPoliciesResourceWithStreamingResponse(self._client.network_policies)

@cached_property
def gateway_configs(self) -> gateway_configs.AsyncGatewayConfigsResourceWithStreamingResponse:
from .resources.gateway_configs import AsyncGatewayConfigsResourceWithStreamingResponse

return AsyncGatewayConfigsResourceWithStreamingResponse(self._client.gateway_configs)


Client = Runloop

Expand Down
6 changes: 3 additions & 3 deletions src/runloop_api_client/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def model_dump(
exclude_defaults: bool = False,
warnings: bool = True,
mode: Literal["json", "python"] = "python",
by_alias: bool | None = None,
) -> dict[str, Any]:
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
return model.model_dump(
Expand All @@ -148,13 +149,12 @@ def model_dump(
exclude_defaults=exclude_defaults,
# warnings are not supported in Pydantic v1
warnings=True if PYDANTIC_V1 else warnings,
by_alias=by_alias,
)
return cast(
"dict[str, Any]",
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
exclude=exclude,
exclude_unset=exclude_unset,
exclude_defaults=exclude_defaults,
exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
),
)

Expand Down
35 changes: 35 additions & 0 deletions src/runloop_api_client/_utils/_json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import json
from typing import Any
from datetime import datetime
from typing_extensions import override

import pydantic

from .._compat import model_dump


def openapi_dumps(obj: Any) -> bytes:
"""
Serialize an object to UTF-8 encoded JSON bytes.
Extends the standard json.dumps with support for additional types
commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
"""
return json.dumps(
obj,
cls=_CustomEncoder,
# Uses the same defaults as httpx's JSON serialization
ensure_ascii=False,
separators=(",", ":"),
allow_nan=False,
).encode()


class _CustomEncoder(json.JSONEncoder):
@override
def default(self, o: Any) -> Any:
if isinstance(o, datetime):
return o.isoformat()
if isinstance(o, pydantic.BaseModel):
return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
return super().default(o)
2 changes: 1 addition & 1 deletion src/runloop_api_client/_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__ = "runloop_api_client"
__version__ = "1.3.2" # x-release-please-version
__version__ = "1.4.0" # x-release-please-version
75 changes: 75 additions & 0 deletions src/runloop_api_client/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
"AsyncObjectsCursorIDPage",
"SyncNetworkPoliciesCursorIDPage",
"AsyncNetworkPoliciesCursorIDPage",
"SyncGatewayConfigsCursorIDPage",
"AsyncGatewayConfigsCursorIDPage",
]

_T = TypeVar("_T")
Expand Down Expand Up @@ -95,6 +97,11 @@ class NetworkPoliciesCursorIDPageItem(Protocol):
id: str


@runtime_checkable
class GatewayConfigsCursorIDPageItem(Protocol):
id: str


class SyncBlueprintsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
blueprints: List[_T]
has_more: Optional[bool] = None
Expand Down Expand Up @@ -909,3 +916,71 @@ def next_page_info(self) -> Optional[PageInfo]:
return None

return PageInfo(params={"starting_after": item.id})


class SyncGatewayConfigsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
gateway_configs: List[_T]
has_more: Optional[bool] = None
total_count: Optional[int] = None

@override
def _get_page_items(self) -> List[_T]:
gateway_configs = self.gateway_configs
if not gateway_configs:
return []
return gateway_configs

@override
def has_next_page(self) -> bool:
has_more = self.has_more
if has_more is not None and has_more is False:
return False

return super().has_next_page()

@override
def next_page_info(self) -> Optional[PageInfo]:
gateway_configs = self.gateway_configs
if not gateway_configs:
return None

item = cast(Any, gateway_configs[-1])
if not isinstance(item, GatewayConfigsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
# TODO emit warning log
return None

return PageInfo(params={"starting_after": item.id})


class AsyncGatewayConfigsCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
gateway_configs: List[_T]
has_more: Optional[bool] = None
total_count: Optional[int] = None

@override
def _get_page_items(self) -> List[_T]:
gateway_configs = self.gateway_configs
if not gateway_configs:
return []
return gateway_configs

@override
def has_next_page(self) -> bool:
has_more = self.has_more
if has_more is not None and has_more is False:
return False

return super().has_next_page()

@override
def next_page_info(self) -> Optional[PageInfo]:
gateway_configs = self.gateway_configs
if not gateway_configs:
return None

item = cast(Any, gateway_configs[-1])
if not isinstance(item, GatewayConfigsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
# TODO emit warning log
return None

return PageInfo(params={"starting_after": item.id})
14 changes: 14 additions & 0 deletions src/runloop_api_client/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
BenchmarkRunsResourceWithStreamingResponse,
AsyncBenchmarkRunsResourceWithStreamingResponse,
)
from .gateway_configs import (
GatewayConfigsResource,
AsyncGatewayConfigsResource,
GatewayConfigsResourceWithRawResponse,
AsyncGatewayConfigsResourceWithRawResponse,
GatewayConfigsResourceWithStreamingResponse,
AsyncGatewayConfigsResourceWithStreamingResponse,
)
from .network_policies import (
NetworkPoliciesResource,
AsyncNetworkPoliciesResource,
Expand Down Expand Up @@ -156,4 +164,10 @@
"AsyncNetworkPoliciesResourceWithRawResponse",
"NetworkPoliciesResourceWithStreamingResponse",
"AsyncNetworkPoliciesResourceWithStreamingResponse",
"GatewayConfigsResource",
"AsyncGatewayConfigsResource",
"GatewayConfigsResourceWithRawResponse",
"AsyncGatewayConfigsResourceWithRawResponse",
"GatewayConfigsResourceWithStreamingResponse",
"AsyncGatewayConfigsResourceWithStreamingResponse",
]
Loading
Loading