From 7ab29ec5417489f1a2b168a9bc6d3f3801d250e6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 14 Oct 2025 06:44:09 +0000
Subject: [PATCH 01/31] feat(api): api update
---
.stats.yml | 8 +-
api.md | 2 +
src/gbox_sdk/resources/v1/boxes/android.py | 96 ++++++++++++++++
src/gbox_sdk/types/v1/boxes/__init__.py | 2 +
.../v1/boxes/android_appium_url_params.py | 18 +++
.../v1/boxes/android_appium_url_response.py | 18 +++
tests/api_resources/v1/boxes/test_android.py | 103 ++++++++++++++++++
7 files changed, 243 insertions(+), 4 deletions(-)
create mode 100644 src/gbox_sdk/types/v1/boxes/android_appium_url_params.py
create mode 100644 src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
diff --git a/.stats.yml b/.stats.yml
index 9f14195c..6a0609ed 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 91
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-0745319813b558c68465ca4c9dbd22b7cc67dd2ef4e346432926f37170c76519.yml
-openapi_spec_hash: 9585c44f5de23369fac5cddadb59d32b
-config_hash: 8c16f9981d464cee8959ebfef30eb1b7
+configured_endpoints: 92
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-c00f53db8cf5c7ef75eb7e140410f5773253fa8a87774b0fa2de364124419f6c.yml
+openapi_spec_hash: 378ba4fda1919d6f1202f4fb89012046
+config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
diff --git a/api.md b/api.md
index bcb3af52..2a291a7d 100644
--- a/api.md
+++ b/api.md
@@ -228,6 +228,7 @@ Types:
from gbox_sdk.types.v1.boxes import (
AndroidApp,
AndroidPkg,
+ AndroidAppiumURLResponse,
AndroidGetConnectAddressResponse,
AndroidInstallResponse,
AndroidListActivitiesResponse,
@@ -239,6 +240,7 @@ from gbox_sdk.types.v1.boxes import (
Methods:
+- client.v1.boxes.android.appium_url(box_id, \*\*params) -> AndroidAppiumURLResponse
- client.v1.boxes.android.backup(package_name, \*, box_id) -> BinaryAPIResponse
- client.v1.boxes.android.backup_all(box_id) -> BinaryAPIResponse
- client.v1.boxes.android.close(package_name, \*, box_id) -> None
diff --git a/src/gbox_sdk/resources/v1/boxes/android.py b/src/gbox_sdk/resources/v1/boxes/android.py
index 0d1371c0..ee81724e 100644
--- a/src/gbox_sdk/resources/v1/boxes/android.py
+++ b/src/gbox_sdk/resources/v1/boxes/android.py
@@ -33,6 +33,7 @@
android_restore_params,
android_list_pkg_params,
android_uninstall_params,
+ android_appium_url_params,
android_list_pkg_simple_params,
)
from ....types.v1.boxes.android_app import AndroidApp
@@ -40,6 +41,7 @@
from ....types.v1.boxes.android_install_response import AndroidInstallResponse
from ....types.v1.boxes.android_list_app_response import AndroidListAppResponse
from ....types.v1.boxes.android_list_pkg_response import AndroidListPkgResponse
+from ....types.v1.boxes.android_appium_url_response import AndroidAppiumURLResponse
from ....types.v1.boxes.android_list_activities_response import AndroidListActivitiesResponse
from ....types.v1.boxes.android_list_pkg_simple_response import AndroidListPkgSimpleResponse
from ....types.v1.boxes.android_get_connect_address_response import AndroidGetConnectAddressResponse
@@ -67,6 +69,46 @@ def with_streaming_response(self) -> AndroidResourceWithStreamingResponse:
"""
return AndroidResourceWithStreamingResponse(self)
+ def appium_url(
+ self,
+ box_id: str,
+ *,
+ expires_in: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AndroidAppiumURLResponse:
+ """
+ Generate a pre-signed proxy URL for Appium server of a running Android box.
+
+ Args:
+ expires_in: The Appium connection url will be alive for the given duration
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 120m
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not box_id:
+ raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
+ return self._post(
+ f"/boxes/{box_id}/android/connect-url/appium",
+ body=maybe_transform({"expires_in": expires_in}, android_appium_url_params.AndroidAppiumURLParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AndroidAppiumURLResponse,
+ )
+
def backup(
self,
package_name: str,
@@ -820,6 +862,48 @@ def with_streaming_response(self) -> AsyncAndroidResourceWithStreamingResponse:
"""
return AsyncAndroidResourceWithStreamingResponse(self)
+ async def appium_url(
+ self,
+ box_id: str,
+ *,
+ expires_in: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> AndroidAppiumURLResponse:
+ """
+ Generate a pre-signed proxy URL for Appium server of a running Android box.
+
+ Args:
+ expires_in: The Appium connection url will be alive for the given duration
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 120m
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not box_id:
+ raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
+ return await self._post(
+ f"/boxes/{box_id}/android/connect-url/appium",
+ body=await async_maybe_transform(
+ {"expires_in": expires_in}, android_appium_url_params.AndroidAppiumURLParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AndroidAppiumURLResponse,
+ )
+
async def backup(
self,
package_name: str,
@@ -1559,6 +1643,9 @@ class AndroidResourceWithRawResponse:
def __init__(self, android: AndroidResource) -> None:
self._android = android
+ self.appium_url = to_raw_response_wrapper(
+ android.appium_url,
+ )
self.backup = to_custom_raw_response_wrapper(
android.backup,
BinaryAPIResponse,
@@ -1615,6 +1702,9 @@ class AsyncAndroidResourceWithRawResponse:
def __init__(self, android: AsyncAndroidResource) -> None:
self._android = android
+ self.appium_url = async_to_raw_response_wrapper(
+ android.appium_url,
+ )
self.backup = async_to_custom_raw_response_wrapper(
android.backup,
AsyncBinaryAPIResponse,
@@ -1671,6 +1761,9 @@ class AndroidResourceWithStreamingResponse:
def __init__(self, android: AndroidResource) -> None:
self._android = android
+ self.appium_url = to_streamed_response_wrapper(
+ android.appium_url,
+ )
self.backup = to_custom_streamed_response_wrapper(
android.backup,
StreamedBinaryAPIResponse,
@@ -1727,6 +1820,9 @@ class AsyncAndroidResourceWithStreamingResponse:
def __init__(self, android: AsyncAndroidResource) -> None:
self._android = android
+ self.appium_url = async_to_streamed_response_wrapper(
+ android.appium_url,
+ )
self.backup = async_to_custom_streamed_response_wrapper(
android.backup,
AsyncStreamedBinaryAPIResponse,
diff --git a/src/gbox_sdk/types/v1/boxes/__init__.py b/src/gbox_sdk/types/v1/boxes/__init__.py
index f4f90ea6..d868dd23 100644
--- a/src/gbox_sdk/types/v1/boxes/__init__.py
+++ b/src/gbox_sdk/types/v1/boxes/__init__.py
@@ -56,6 +56,7 @@
from .browser_cdp_url_response import BrowserCdpURLResponse as BrowserCdpURLResponse
from .browser_set_proxy_params import BrowserSetProxyParams as BrowserSetProxyParams
from .media_get_media_response import MediaGetMediaResponse as MediaGetMediaResponse
+from .android_appium_url_params import AndroidAppiumURLParams as AndroidAppiumURLParams
from .android_list_app_response import AndroidListAppResponse as AndroidListAppResponse
from .android_list_pkg_response import AndroidListPkgResponse as AndroidListPkgResponse
from .browser_get_tabs_response import BrowserGetTabsResponse as BrowserGetTabsResponse
@@ -71,6 +72,7 @@
from .media_list_albums_response import MediaListAlbumsResponse as MediaListAlbumsResponse
from .action_clipboard_set_params import ActionClipboardSetParams as ActionClipboardSetParams
from .action_common_options_param import ActionCommonOptionsParam as ActionCommonOptionsParam
+from .android_appium_url_response import AndroidAppiumURLResponse as AndroidAppiumURLResponse
from .browser_switch_tab_response import BrowserSwitchTabResponse as BrowserSwitchTabResponse
from .browser_update_tab_response import BrowserUpdateTabResponse as BrowserUpdateTabResponse
from .action_rewind_extract_params import ActionRewindExtractParams as ActionRewindExtractParams
diff --git a/src/gbox_sdk/types/v1/boxes/android_appium_url_params.py b/src/gbox_sdk/types/v1/boxes/android_appium_url_params.py
new file mode 100644
index 00000000..3ff31357
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/android_appium_url_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["AndroidAppiumURLParams"]
+
+
+class AndroidAppiumURLParams(TypedDict, total=False):
+ expires_in: Annotated[str, PropertyInfo(alias="expiresIn")]
+ """The Appium connection url will be alive for the given duration
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 120m
+ """
diff --git a/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
new file mode 100644
index 00000000..14293bf2
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["AndroidAppiumURLResponse"]
+
+
+class AndroidAppiumURLResponse(BaseModel):
+ default_option: object = FieldInfo(alias="defaultOption")
+ """A ready-to-use default WebdriverIO remote options object"""
+
+ udid: str
+ """Device UDID for Appium connection"""
+
+ url: str
+ """Appium connection URL"""
diff --git a/tests/api_resources/v1/boxes/test_android.py b/tests/api_resources/v1/boxes/test_android.py
index 2516b2b8..98f3287e 100644
--- a/tests/api_resources/v1/boxes/test_android.py
+++ b/tests/api_resources/v1/boxes/test_android.py
@@ -23,6 +23,7 @@
AndroidInstallResponse,
AndroidListAppResponse,
AndroidListPkgResponse,
+ AndroidAppiumURLResponse,
AndroidListPkgSimpleResponse,
AndroidListActivitiesResponse,
AndroidGetConnectAddressResponse,
@@ -34,6 +35,57 @@
class TestAndroid:
parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_appium_url(self, client: GboxClient) -> None:
+ android = client.v1.boxes.android.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ )
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_appium_url_with_all_params(self, client: GboxClient) -> None:
+ android = client.v1.boxes.android.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ expires_in="120m",
+ )
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_appium_url(self, client: GboxClient) -> None:
+ response = client.v1.boxes.android.with_raw_response.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ android = response.parse()
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_appium_url(self, client: GboxClient) -> None:
+ with client.v1.boxes.android.with_streaming_response.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ android = response.parse()
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_appium_url(self, client: GboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `box_id` but received ''"):
+ client.v1.boxes.android.with_raw_response.appium_url(
+ box_id="",
+ )
+
@parametrize
@pytest.mark.respx(base_url=base_url)
def test_method_backup(self, client: GboxClient, respx_mock: MockRouter) -> None:
@@ -943,6 +995,57 @@ class TestAsyncAndroid:
"async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
)
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_appium_url(self, async_client: AsyncGboxClient) -> None:
+ android = await async_client.v1.boxes.android.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ )
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_appium_url_with_all_params(self, async_client: AsyncGboxClient) -> None:
+ android = await async_client.v1.boxes.android.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ expires_in="120m",
+ )
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_appium_url(self, async_client: AsyncGboxClient) -> None:
+ response = await async_client.v1.boxes.android.with_raw_response.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ android = await response.parse()
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_appium_url(self, async_client: AsyncGboxClient) -> None:
+ async with async_client.v1.boxes.android.with_streaming_response.appium_url(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ android = await response.parse()
+ assert_matches_type(AndroidAppiumURLResponse, android, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_appium_url(self, async_client: AsyncGboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `box_id` but received ''"):
+ await async_client.v1.boxes.android.with_raw_response.appium_url(
+ box_id="",
+ )
+
@parametrize
@pytest.mark.respx(base_url=base_url)
async def test_method_backup(self, async_client: AsyncGboxClient, respx_mock: MockRouter) -> None:
From 874ec440b07600afbc0974fab08bdf477936133c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 15 Oct 2025 02:30:25 +0000
Subject: [PATCH 02/31] feat(api): api update
---
.stats.yml | 4 +--
src/gbox_sdk/resources/v1/devices.py | 10 ++++----
src/gbox_sdk/types/v1/device_list_params.py | 6 ++---
tests/api_resources/v1/test_devices.py | 28 ++++++---------------
4 files changed, 18 insertions(+), 30 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 6a0609ed..4d0da602 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-c00f53db8cf5c7ef75eb7e140410f5773253fa8a87774b0fa2de364124419f6c.yml
-openapi_spec_hash: 378ba4fda1919d6f1202f4fb89012046
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-cea48a6d14d1dcb4f0536c066ae8a838da3188713cdb3b7f60c264ebe706e9aa.yml
+openapi_spec_hash: 3ff6b14fb85ca1ff0117c59ae6c8cf17
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
diff --git a/src/gbox_sdk/resources/v1/devices.py b/src/gbox_sdk/resources/v1/devices.py
index 1a11508e..c3fa8b35 100644
--- a/src/gbox_sdk/resources/v1/devices.py
+++ b/src/gbox_sdk/resources/v1/devices.py
@@ -5,7 +5,7 @@
import httpx
from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
-from ..._utils import maybe_transform, async_maybe_transform
+from ..._utils import maybe_transform, strip_not_given, async_maybe_transform
from ..._compat import cached_property
from ...types.v1 import device_list_params, device_to_box_params
from ..._resource import SyncAPIResource, AsyncAPIResource
@@ -45,9 +45,9 @@ def with_streaming_response(self) -> DevicesResourceWithStreamingResponse:
def list(
self,
*,
- x_device_ap: str,
page: int | Omit = omit,
page_size: int | Omit = omit,
+ x_device_ap: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -71,7 +71,7 @@ def list(
timeout: Override the client-level default timeout for this request, in seconds
"""
- extra_headers = {"x-device-ap": x_device_ap, **(extra_headers or {})}
+ extra_headers = {**strip_not_given({"x-device-ap": x_device_ap}), **(extra_headers or {})}
return self._get(
"/devices",
options=make_request_options(
@@ -187,9 +187,9 @@ def with_streaming_response(self) -> AsyncDevicesResourceWithStreamingResponse:
async def list(
self,
*,
- x_device_ap: str,
page: int | Omit = omit,
page_size: int | Omit = omit,
+ x_device_ap: str | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Headers | None = None,
@@ -213,7 +213,7 @@ async def list(
timeout: Override the client-level default timeout for this request, in seconds
"""
- extra_headers = {"x-device-ap": x_device_ap, **(extra_headers or {})}
+ extra_headers = {**strip_not_given({"x-device-ap": x_device_ap}), **(extra_headers or {})}
return await self._get(
"/devices",
options=make_request_options(
diff --git a/src/gbox_sdk/types/v1/device_list_params.py b/src/gbox_sdk/types/v1/device_list_params.py
index 432ac355..769efc22 100644
--- a/src/gbox_sdk/types/v1/device_list_params.py
+++ b/src/gbox_sdk/types/v1/device_list_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing_extensions import Required, Annotated, TypedDict
+from typing_extensions import Annotated, TypedDict
from ..._utils import PropertyInfo
@@ -10,10 +10,10 @@
class DeviceListParams(TypedDict, total=False):
- x_device_ap: Required[Annotated[str, PropertyInfo(alias="x-device-ap")]]
-
page: int
"""Page number"""
page_size: Annotated[int, PropertyInfo(alias="pageSize")]
"""Page size"""
+
+ x_device_ap: Annotated[str, PropertyInfo(alias="x-device-ap")]
diff --git a/tests/api_resources/v1/test_devices.py b/tests/api_resources/v1/test_devices.py
index 2d3cc4b2..3994163e 100644
--- a/tests/api_resources/v1/test_devices.py
+++ b/tests/api_resources/v1/test_devices.py
@@ -23,27 +23,23 @@ class TestDevices:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list(self, client: GboxClient) -> None:
- device = client.v1.devices.list(
- x_device_ap="x-device-ap",
- )
+ device = client.v1.devices.list()
assert_matches_type(GetDeviceListResponse, device, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list_with_all_params(self, client: GboxClient) -> None:
device = client.v1.devices.list(
- x_device_ap="x-device-ap",
page=1,
page_size=10,
+ x_device_ap="x-device-ap",
)
assert_matches_type(GetDeviceListResponse, device, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_raw_response_list(self, client: GboxClient) -> None:
- response = client.v1.devices.with_raw_response.list(
- x_device_ap="x-device-ap",
- )
+ response = client.v1.devices.with_raw_response.list()
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -53,9 +49,7 @@ def test_raw_response_list(self, client: GboxClient) -> None:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_streaming_response_list(self, client: GboxClient) -> None:
- with client.v1.devices.with_streaming_response.list(
- x_device_ap="x-device-ap",
- ) as response:
+ with client.v1.devices.with_streaming_response.list() as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -166,27 +160,23 @@ class TestAsyncDevices:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list(self, async_client: AsyncGboxClient) -> None:
- device = await async_client.v1.devices.list(
- x_device_ap="x-device-ap",
- )
+ device = await async_client.v1.devices.list()
assert_matches_type(GetDeviceListResponse, device, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncGboxClient) -> None:
device = await async_client.v1.devices.list(
- x_device_ap="x-device-ap",
page=1,
page_size=10,
+ x_device_ap="x-device-ap",
)
assert_matches_type(GetDeviceListResponse, device, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_raw_response_list(self, async_client: AsyncGboxClient) -> None:
- response = await async_client.v1.devices.with_raw_response.list(
- x_device_ap="x-device-ap",
- )
+ response = await async_client.v1.devices.with_raw_response.list()
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -196,9 +186,7 @@ async def test_raw_response_list(self, async_client: AsyncGboxClient) -> None:
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_streaming_response_list(self, async_client: AsyncGboxClient) -> None:
- async with async_client.v1.devices.with_streaming_response.list(
- x_device_ap="x-device-ap",
- ) as response:
+ async with async_client.v1.devices.with_streaming_response.list() as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
From 104e0358864a59ce0580a56850d673e551989ade Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 15 Oct 2025 04:56:17 +0000
Subject: [PATCH 03/31] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 4d0da602..9f807681 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-cea48a6d14d1dcb4f0536c066ae8a838da3188713cdb3b7f60c264ebe706e9aa.yml
-openapi_spec_hash: 3ff6b14fb85ca1ff0117c59ae6c8cf17
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-98abae05e813c322ba9477a12b298636c740bccbcb1d0654603d6965ca73242e.yml
+openapi_spec_hash: c7b54fd7889afb2c40606b68f765d2aa
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
From a4fa084a0028968e2c5c6bffb84990d24cb32820 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 15 Oct 2025 05:24:03 +0000
Subject: [PATCH 04/31] feat(api): api update
---
.stats.yml | 4 +-
.../v1/boxes/android_appium_url_response.py | 37 +++++++++++++++++--
2 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 9f807681..604bab39 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-98abae05e813c322ba9477a12b298636c740bccbcb1d0654603d6965ca73242e.yml
-openapi_spec_hash: c7b54fd7889afb2c40606b68f765d2aa
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-8c2ae564f6dfa496b6215dec5d4184cb4fde5dc588d701ad88fde769a4be6bfc.yml
+openapi_spec_hash: c78da9e9242c1783a361912102231f25
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
diff --git a/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
index 14293bf2..660eec60 100644
--- a/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
+++ b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
@@ -4,12 +4,43 @@
from ...._models import BaseModel
-__all__ = ["AndroidAppiumURLResponse"]
+__all__ = ["AndroidAppiumURLResponse", "DefaultOption", "DefaultOptionCapabilities"]
+
+
+class DefaultOptionCapabilities(BaseModel):
+ appium_automation_name: str = FieldInfo(alias="appium:automationName")
+ """Appium automation name"""
+
+ appium_device_name: str = FieldInfo(alias="appium:deviceName")
+ """Device name"""
+
+ appium_udid: str = FieldInfo(alias="appium:udid")
+ """Device UDID"""
+
+ platform_name: str = FieldInfo(alias="platformName")
+ """Platform name"""
+
+
+class DefaultOption(BaseModel):
+ capabilities: DefaultOptionCapabilities
+ """Appium capabilities for WebdriverIO"""
+
+ hostname: str
+ """Hostname"""
+
+ path: str
+ """URL pathname"""
+
+ port: float
+ """Port number"""
+
+ protocol: str
+ """Protocol (http or https)"""
class AndroidAppiumURLResponse(BaseModel):
- default_option: object = FieldInfo(alias="defaultOption")
- """A ready-to-use default WebdriverIO remote options object"""
+ default_option: DefaultOption = FieldInfo(alias="defaultOption")
+ """Ready-to-use WebdriverIO remote options"""
udid: str
"""Device UDID for Appium connection"""
From 3d5ce673d79761e6d30008545362a5d3b003063b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 15 Oct 2025 07:50:44 +0000
Subject: [PATCH 05/31] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 604bab39..5bbd943b 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-8c2ae564f6dfa496b6215dec5d4184cb4fde5dc588d701ad88fde769a4be6bfc.yml
-openapi_spec_hash: c78da9e9242c1783a361912102231f25
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-d6b6308b6bf7e0920993d36feb4ebc5d1fe341ee61a32cb5b972df487dcb8e87.yml
+openapi_spec_hash: 55ef8c0c34207b3ba257f60f4f6bfc3a
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
From 21a9d25006827c3aa81e394dc4683f944d120870 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 15 Oct 2025 09:16:03 +0000
Subject: [PATCH 06/31] feat(api): api update
---
.stats.yml | 4 ++--
src/gbox_sdk/types/v1/box_create_android_params.py | 7 +++++++
src/gbox_sdk/types/v1/box_create_linux_params.py | 7 +++++++
tests/api_resources/v1/test_boxes.py | 4 ++++
4 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 5bbd943b..a3389935 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-d6b6308b6bf7e0920993d36feb4ebc5d1fe341ee61a32cb5b972df487dcb8e87.yml
-openapi_spec_hash: 55ef8c0c34207b3ba257f60f4f6bfc3a
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-28e5823f39385ae63dafe7146c640ad94b25228c605fd56541fb616640c54251.yml
+openapi_spec_hash: e9caaf9f975085f80d275251226379fe
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
diff --git a/src/gbox_sdk/types/v1/box_create_android_params.py b/src/gbox_sdk/types/v1/box_create_android_params.py
index 501f6f5c..8d0487d3 100644
--- a/src/gbox_sdk/types/v1/box_create_android_params.py
+++ b/src/gbox_sdk/types/v1/box_create_android_params.py
@@ -47,6 +47,13 @@ class Config(TypedDict, total=False):
Example formats: "500ms", "30s", "5m", "1h" Default: 60m
"""
+ keep_alive: Annotated[str, PropertyInfo(alias="keepAlive")]
+ """Keep alive duration on activity; 0 disables keep alive
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 0ms
+ """
+
labels: Dict[str, str]
"""Key-value pairs of labels for the box.
diff --git a/src/gbox_sdk/types/v1/box_create_linux_params.py b/src/gbox_sdk/types/v1/box_create_linux_params.py
index cead505d..19ec5e16 100644
--- a/src/gbox_sdk/types/v1/box_create_linux_params.py
+++ b/src/gbox_sdk/types/v1/box_create_linux_params.py
@@ -44,6 +44,13 @@ class Config(TypedDict, total=False):
Example formats: "500ms", "30s", "5m", "1h" Default: 60m
"""
+ keep_alive: Annotated[str, PropertyInfo(alias="keepAlive")]
+ """Keep alive duration on activity; 0 disables keep alive
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 0ms
+ """
+
labels: Dict[str, str]
"""Key-value pairs of labels for the box.
diff --git a/tests/api_resources/v1/test_boxes.py b/tests/api_resources/v1/test_boxes.py
index e77083e5..4e4a59e6 100644
--- a/tests/api_resources/v1/test_boxes.py
+++ b/tests/api_resources/v1/test_boxes.py
@@ -131,6 +131,7 @@ def test_method_create_android_with_all_params(self, client: GboxClient) -> None
"ADB_TRACE": "all",
},
"expires_in": "15m",
+ "keep_alive": "0ms",
"labels": {
"app": "mobile-testing",
"version": "v1.0",
@@ -179,6 +180,7 @@ def test_method_create_linux_with_all_params(self, client: GboxClient) -> None:
"API_URL": "https://api.example.com",
},
"expires_in": "60m",
+ "keep_alive": "0ms",
"labels": {
"project": "web-automation",
"environment": "testing",
@@ -830,6 +832,7 @@ async def test_method_create_android_with_all_params(self, async_client: AsyncGb
"ADB_TRACE": "all",
},
"expires_in": "15m",
+ "keep_alive": "0ms",
"labels": {
"app": "mobile-testing",
"version": "v1.0",
@@ -878,6 +881,7 @@ async def test_method_create_linux_with_all_params(self, async_client: AsyncGbox
"API_URL": "https://api.example.com",
},
"expires_in": "60m",
+ "keep_alive": "0ms",
"labels": {
"project": "web-automation",
"environment": "testing",
From bf80a71aac618785c3e3775be0ec47c2f740b636 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 16 Oct 2025 02:25:56 +0000
Subject: [PATCH 07/31] feat(api): api update
---
.stats.yml | 4 ++--
src/gbox_sdk/types/v1/box_create_android_params.py | 11 ++++++++++-
src/gbox_sdk/types/v1/box_create_linux_params.py | 11 ++++++++++-
3 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index a3389935..b4c1fa0e 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-28e5823f39385ae63dafe7146c640ad94b25228c605fd56541fb616640c54251.yml
-openapi_spec_hash: e9caaf9f975085f80d275251226379fe
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-db59f688c875a646179cee65ed329cf008ad8600db36ceae11b2972cc7646ccc.yml
+openapi_spec_hash: 3a78f321b58dbe5aa77a8c5f88c219ef
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
diff --git a/src/gbox_sdk/types/v1/box_create_android_params.py b/src/gbox_sdk/types/v1/box_create_android_params.py
index 8d0487d3..54d59c69 100644
--- a/src/gbox_sdk/types/v1/box_create_android_params.py
+++ b/src/gbox_sdk/types/v1/box_create_android_params.py
@@ -48,7 +48,16 @@ class Config(TypedDict, total=False):
"""
keep_alive: Annotated[str, PropertyInfo(alias="keepAlive")]
- """Keep alive duration on activity; 0 disables keep alive
+ """Keep alive duration on activity.
+
+ When set to a positive value (e.g., '5m'), the box expiration time (expiresIn)
+ will be automatically extended to ensure at least this duration remains whenever
+ there is an box operation on this specific box. For example, when calling UI
+ Action, FS, Browser, Command, Media, or Run Code operations with this box's
+ boxId, the box will be kept alive. If keepAlive is '5m' and the box has 2
+ minutes remaining, any operation on this boxId will extend the remaining time to
+ 5 minutes. Set to '0ms' to disable automatic keep alive extension. This helps
+ keep frequently-used boxes alive without manual intervention.
Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
Example formats: "500ms", "30s", "5m", "1h" Default: 0ms
diff --git a/src/gbox_sdk/types/v1/box_create_linux_params.py b/src/gbox_sdk/types/v1/box_create_linux_params.py
index 19ec5e16..335a656f 100644
--- a/src/gbox_sdk/types/v1/box_create_linux_params.py
+++ b/src/gbox_sdk/types/v1/box_create_linux_params.py
@@ -45,7 +45,16 @@ class Config(TypedDict, total=False):
"""
keep_alive: Annotated[str, PropertyInfo(alias="keepAlive")]
- """Keep alive duration on activity; 0 disables keep alive
+ """Keep alive duration on activity.
+
+ When set to a positive value (e.g., '5m'), the box expiration time (expiresIn)
+ will be automatically extended to ensure at least this duration remains whenever
+ there is an box operation on this specific box. For example, when calling UI
+ Action, FS, Browser, Command, Media, or Run Code operations with this box's
+ boxId, the box will be kept alive. If keepAlive is '5m' and the box has 2
+ minutes remaining, any operation on this boxId will extend the remaining time to
+ 5 minutes. Set to '0ms' to disable automatic keep alive extension. This helps
+ keep frequently-used boxes alive without manual intervention.
Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
Example formats: "500ms", "30s", "5m", "1h" Default: 0ms
From 69414a0450da6b6a2f77aefefa25e925fabbc12c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 17 Oct 2025 08:50:37 +0000
Subject: [PATCH 08/31] feat(api): api update
---
.stats.yml | 4 ++--
src/gbox_sdk/types/v1/boxes/android_appium_url_response.py | 5 +++++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index b4c1fa0e..797c797d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-db59f688c875a646179cee65ed329cf008ad8600db36ceae11b2972cc7646ccc.yml
-openapi_spec_hash: 3a78f321b58dbe5aa77a8c5f88c219ef
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-f72fd628309315699dfb7b043b36760cdb7c68a286d8fb98aa499ec7673efd9c.yml
+openapi_spec_hash: 7d8ebfdffa9c12148bddffb303940fac
config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
diff --git a/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
index 660eec60..6619646e 100644
--- a/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
+++ b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
@@ -1,5 +1,7 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing_extensions import Literal
+
from pydantic import Field as FieldInfo
from ...._models import BaseModel
@@ -42,6 +44,9 @@ class AndroidAppiumURLResponse(BaseModel):
default_option: DefaultOption = FieldInfo(alias="defaultOption")
"""Ready-to-use WebdriverIO remote options"""
+ log_level: Literal["trace", "debug", "info", "warn", "error", "silent"] = FieldInfo(alias="logLevel")
+ """Log level for WebdriverIO/Appium client"""
+
udid: str
"""Device UDID for Appium connection"""
From d3b0ab939b6ea1ba076374da27f8ea13813b3a45 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 18 Oct 2025 02:05:32 +0000
Subject: [PATCH 09/31] chore: bump `httpx-aiohttp` version to 0.1.9
---
pyproject.toml | 2 +-
requirements-dev.lock | 2 +-
requirements.lock | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index d30a5df1..d220319e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,7 +39,7 @@ Homepage = "https://github.com/babelcloud/gbox-sdk-py"
Repository = "https://github.com/babelcloud/gbox-sdk-py"
[project.optional-dependencies]
-aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"]
+aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"]
websocket-client = ["websocket-client>=1.0.0, <2"]
[tool.rye]
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 1c6f64d1..e8958ad9 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -56,7 +56,7 @@ httpx==0.28.1
# via gbox-sdk
# via httpx-aiohttp
# via respx
-httpx-aiohttp==0.1.8
+httpx-aiohttp==0.1.9
# via gbox-sdk
idna==3.4
# via anyio
diff --git a/requirements.lock b/requirements.lock
index d2bd0dad..a621fea9 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -43,7 +43,7 @@ httpcore==1.0.9
httpx==0.28.1
# via gbox-sdk
# via httpx-aiohttp
-httpx-aiohttp==0.1.8
+httpx-aiohttp==0.1.9
# via gbox-sdk
idna==3.4
# via anyio
From 68ce1621ebd5200b9da3c5d33d7e66e31a0267c9 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Oct 2025 07:06:09 +0000
Subject: [PATCH 10/31] feat(api): api update
---
.stats.yml | 8 +-
api.md | 12 ++
src/gbox_sdk/resources/v1/__init__.py | 14 ++
src/gbox_sdk/resources/v1/models.py | 195 +++++++++++++++++++
src/gbox_sdk/resources/v1/v1.py | 32 +++
src/gbox_sdk/types/v1/__init__.py | 2 +
src/gbox_sdk/types/v1/model_call_params.py | 18 ++
src/gbox_sdk/types/v1/model_call_response.py | 13 ++
tests/api_resources/v1/test_models.py | 142 ++++++++++++++
9 files changed, 432 insertions(+), 4 deletions(-)
create mode 100644 src/gbox_sdk/resources/v1/models.py
create mode 100644 src/gbox_sdk/types/v1/model_call_params.py
create mode 100644 src/gbox_sdk/types/v1/model_call_response.py
create mode 100644 tests/api_resources/v1/test_models.py
diff --git a/.stats.yml b/.stats.yml
index 797c797d..c4d74cd4 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 92
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-f72fd628309315699dfb7b043b36760cdb7c68a286d8fb98aa499ec7673efd9c.yml
-openapi_spec_hash: 7d8ebfdffa9c12148bddffb303940fac
-config_hash: 0bac971f90a4ab4bc488c14246cc5f1a
+configured_endpoints: 93
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-94f652aed9166bb5cc03ef1a61a01a58537d1005c6c8af981cb5f0d2d60f0042.yml
+openapi_spec_hash: 9b1f6fd60387247c33622af8ad4687ab
+config_hash: 09531a3b47b37eecfb541e383d99e62c
diff --git a/api.md b/api.md
index 2a291a7d..c974c050 100644
--- a/api.md
+++ b/api.md
@@ -14,6 +14,18 @@ Methods:
- client.v1.devices.get(device_id) -> DeviceInfo
- client.v1.devices.to_box(device_id, \*\*params) -> str
+## Models
+
+Types:
+
+```python
+from gbox_sdk.types.v1 import ModelCallResponse
+```
+
+Methods:
+
+- client.v1.models.call(\*\*params) -> ModelCallResponse
+
## Boxes
Types:
diff --git a/src/gbox_sdk/resources/v1/__init__.py b/src/gbox_sdk/resources/v1/__init__.py
index 9599d8fe..48846c8a 100644
--- a/src/gbox_sdk/resources/v1/__init__.py
+++ b/src/gbox_sdk/resources/v1/__init__.py
@@ -16,6 +16,14 @@
BoxesResourceWithStreamingResponse,
AsyncBoxesResourceWithStreamingResponse,
)
+from .models import (
+ ModelsResource,
+ AsyncModelsResource,
+ ModelsResourceWithRawResponse,
+ AsyncModelsResourceWithRawResponse,
+ ModelsResourceWithStreamingResponse,
+ AsyncModelsResourceWithStreamingResponse,
+)
from .devices import (
DevicesResource,
AsyncDevicesResource,
@@ -32,6 +40,12 @@
"AsyncDevicesResourceWithRawResponse",
"DevicesResourceWithStreamingResponse",
"AsyncDevicesResourceWithStreamingResponse",
+ "ModelsResource",
+ "AsyncModelsResource",
+ "ModelsResourceWithRawResponse",
+ "AsyncModelsResourceWithRawResponse",
+ "ModelsResourceWithStreamingResponse",
+ "AsyncModelsResourceWithStreamingResponse",
"BoxesResource",
"AsyncBoxesResource",
"BoxesResourceWithRawResponse",
diff --git a/src/gbox_sdk/resources/v1/models.py b/src/gbox_sdk/resources/v1/models.py
new file mode 100644
index 00000000..19b111fc
--- /dev/null
+++ b/src/gbox_sdk/resources/v1/models.py
@@ -0,0 +1,195 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
+from ..._utils import maybe_transform, async_maybe_transform
+from ..._compat import cached_property
+from ...types.v1 import model_call_params
+from ..._resource import SyncAPIResource, AsyncAPIResource
+from ..._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ..._base_client import make_request_options
+from ...types.v1.model_call_response import ModelCallResponse
+
+__all__ = ["ModelsResource", "AsyncModelsResource"]
+
+
+class ModelsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> ModelsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#accessing-raw-response-data-eg-headers
+ """
+ return ModelsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> ModelsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#with_streaming_response
+ """
+ return ModelsResourceWithStreamingResponse(self)
+
+ def call(
+ self,
+ *,
+ action: object,
+ screenshot: str,
+ model: Literal["gbox-handy-1"] | 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,
+ ) -> ModelCallResponse:
+ """
+ Generate coordinates for a model
+
+ Args:
+ action: Structured action object (click or drag)
+
+ screenshot: HTTP(S) URL to screenshot image
+
+ model: Model to use
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return self._post(
+ "/model",
+ body=maybe_transform(
+ {
+ "action": action,
+ "screenshot": screenshot,
+ "model": model,
+ },
+ model_call_params.ModelCallParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ModelCallResponse,
+ )
+
+
+class AsyncModelsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncModelsResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#accessing-raw-response-data-eg-headers
+ """
+ return AsyncModelsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncModelsResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#with_streaming_response
+ """
+ return AsyncModelsResourceWithStreamingResponse(self)
+
+ async def call(
+ self,
+ *,
+ action: object,
+ screenshot: str,
+ model: Literal["gbox-handy-1"] | 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,
+ ) -> ModelCallResponse:
+ """
+ Generate coordinates for a model
+
+ Args:
+ action: Structured action object (click or drag)
+
+ screenshot: HTTP(S) URL to screenshot image
+
+ model: Model to use
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ return await self._post(
+ "/model",
+ body=await async_maybe_transform(
+ {
+ "action": action,
+ "screenshot": screenshot,
+ "model": model,
+ },
+ model_call_params.ModelCallParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=ModelCallResponse,
+ )
+
+
+class ModelsResourceWithRawResponse:
+ def __init__(self, models: ModelsResource) -> None:
+ self._models = models
+
+ self.call = to_raw_response_wrapper(
+ models.call,
+ )
+
+
+class AsyncModelsResourceWithRawResponse:
+ def __init__(self, models: AsyncModelsResource) -> None:
+ self._models = models
+
+ self.call = async_to_raw_response_wrapper(
+ models.call,
+ )
+
+
+class ModelsResourceWithStreamingResponse:
+ def __init__(self, models: ModelsResource) -> None:
+ self._models = models
+
+ self.call = to_streamed_response_wrapper(
+ models.call,
+ )
+
+
+class AsyncModelsResourceWithStreamingResponse:
+ def __init__(self, models: AsyncModelsResource) -> None:
+ self._models = models
+
+ self.call = async_to_streamed_response_wrapper(
+ models.call,
+ )
diff --git a/src/gbox_sdk/resources/v1/v1.py b/src/gbox_sdk/resources/v1/v1.py
index 107615d4..021037a8 100644
--- a/src/gbox_sdk/resources/v1/v1.py
+++ b/src/gbox_sdk/resources/v1/v1.py
@@ -2,6 +2,14 @@
from __future__ import annotations
+from .models import (
+ ModelsResource,
+ AsyncModelsResource,
+ ModelsResourceWithRawResponse,
+ AsyncModelsResourceWithRawResponse,
+ ModelsResourceWithStreamingResponse,
+ AsyncModelsResourceWithStreamingResponse,
+)
from .devices import (
DevicesResource,
AsyncDevicesResource,
@@ -29,6 +37,10 @@ class V1Resource(SyncAPIResource):
def devices(self) -> DevicesResource:
return DevicesResource(self._client)
+ @cached_property
+ def models(self) -> ModelsResource:
+ return ModelsResource(self._client)
+
@cached_property
def boxes(self) -> BoxesResource:
return BoxesResource(self._client)
@@ -58,6 +70,10 @@ class AsyncV1Resource(AsyncAPIResource):
def devices(self) -> AsyncDevicesResource:
return AsyncDevicesResource(self._client)
+ @cached_property
+ def models(self) -> AsyncModelsResource:
+ return AsyncModelsResource(self._client)
+
@cached_property
def boxes(self) -> AsyncBoxesResource:
return AsyncBoxesResource(self._client)
@@ -90,6 +106,10 @@ def __init__(self, v1: V1Resource) -> None:
def devices(self) -> DevicesResourceWithRawResponse:
return DevicesResourceWithRawResponse(self._v1.devices)
+ @cached_property
+ def models(self) -> ModelsResourceWithRawResponse:
+ return ModelsResourceWithRawResponse(self._v1.models)
+
@cached_property
def boxes(self) -> BoxesResourceWithRawResponse:
return BoxesResourceWithRawResponse(self._v1.boxes)
@@ -103,6 +123,10 @@ def __init__(self, v1: AsyncV1Resource) -> None:
def devices(self) -> AsyncDevicesResourceWithRawResponse:
return AsyncDevicesResourceWithRawResponse(self._v1.devices)
+ @cached_property
+ def models(self) -> AsyncModelsResourceWithRawResponse:
+ return AsyncModelsResourceWithRawResponse(self._v1.models)
+
@cached_property
def boxes(self) -> AsyncBoxesResourceWithRawResponse:
return AsyncBoxesResourceWithRawResponse(self._v1.boxes)
@@ -116,6 +140,10 @@ def __init__(self, v1: V1Resource) -> None:
def devices(self) -> DevicesResourceWithStreamingResponse:
return DevicesResourceWithStreamingResponse(self._v1.devices)
+ @cached_property
+ def models(self) -> ModelsResourceWithStreamingResponse:
+ return ModelsResourceWithStreamingResponse(self._v1.models)
+
@cached_property
def boxes(self) -> BoxesResourceWithStreamingResponse:
return BoxesResourceWithStreamingResponse(self._v1.boxes)
@@ -129,6 +157,10 @@ def __init__(self, v1: AsyncV1Resource) -> None:
def devices(self) -> AsyncDevicesResourceWithStreamingResponse:
return AsyncDevicesResourceWithStreamingResponse(self._v1.devices)
+ @cached_property
+ def models(self) -> AsyncModelsResourceWithStreamingResponse:
+ return AsyncModelsResourceWithStreamingResponse(self._v1.models)
+
@cached_property
def boxes(self) -> AsyncBoxesResourceWithStreamingResponse:
return AsyncBoxesResourceWithStreamingResponse(self._v1.boxes)
diff --git a/src/gbox_sdk/types/v1/__init__.py b/src/gbox_sdk/types/v1/__init__.py
index d13e4d9d..2653a563 100644
--- a/src/gbox_sdk/types/v1/__init__.py
+++ b/src/gbox_sdk/types/v1/__init__.py
@@ -10,9 +10,11 @@
from .box_start_params import BoxStartParams as BoxStartParams
from .box_list_response import BoxListResponse as BoxListResponse
from .box_stop_response import BoxStopResponse as BoxStopResponse
+from .model_call_params import ModelCallParams as ModelCallParams
from .box_start_response import BoxStartResponse as BoxStartResponse
from .device_list_params import DeviceListParams as DeviceListParams
from .box_run_code_params import BoxRunCodeParams as BoxRunCodeParams
+from .model_call_response import ModelCallResponse as ModelCallResponse
from .box_display_response import BoxDisplayResponse as BoxDisplayResponse
from .box_terminate_params import BoxTerminateParams as BoxTerminateParams
from .device_to_box_params import DeviceToBoxParams as DeviceToBoxParams
diff --git a/src/gbox_sdk/types/v1/model_call_params.py b/src/gbox_sdk/types/v1/model_call_params.py
new file mode 100644
index 00000000..532fb995
--- /dev/null
+++ b/src/gbox_sdk/types/v1/model_call_params.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, Required, TypedDict
+
+__all__ = ["ModelCallParams"]
+
+
+class ModelCallParams(TypedDict, total=False):
+ action: Required[object]
+ """Structured action object (click or drag)"""
+
+ screenshot: Required[str]
+ """HTTP(S) URL to screenshot image"""
+
+ model: Literal["gbox-handy-1"]
+ """Model to use"""
diff --git a/src/gbox_sdk/types/v1/model_call_response.py b/src/gbox_sdk/types/v1/model_call_response.py
new file mode 100644
index 00000000..ec219a06
--- /dev/null
+++ b/src/gbox_sdk/types/v1/model_call_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from ..._models import BaseModel
+
+__all__ = ["ModelCallResponse"]
+
+
+class ModelCallResponse(BaseModel):
+ id: str
+ """Unique ID of this request, can be used for issue reporting and feedback"""
+
+ response: object
+ """Model response data"""
diff --git a/tests/api_resources/v1/test_models.py b/tests/api_resources/v1/test_models.py
new file mode 100644
index 00000000..c2dcd9d4
--- /dev/null
+++ b/tests/api_resources/v1/test_models.py
@@ -0,0 +1,142 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gbox_sdk import GboxClient, AsyncGboxClient
+from tests.utils import assert_matches_type
+from gbox_sdk.types.v1 import ModelCallResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestModels:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_call(self, client: GboxClient) -> None:
+ model = client.v1.models.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ )
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_call_with_all_params(self, client: GboxClient) -> None:
+ model = client.v1.models.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ model="gbox-handy-1",
+ )
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_call(self, client: GboxClient) -> None:
+ response = client.v1.models.with_raw_response.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ model = response.parse()
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_call(self, client: GboxClient) -> None:
+ with client.v1.models.with_streaming_response.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ model = response.parse()
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncModels:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_call(self, async_client: AsyncGboxClient) -> None:
+ model = await async_client.v1.models.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ )
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_call_with_all_params(self, async_client: AsyncGboxClient) -> None:
+ model = await async_client.v1.models.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ model="gbox-handy-1",
+ )
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_call(self, async_client: AsyncGboxClient) -> None:
+ response = await async_client.v1.models.with_raw_response.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ model = await response.parse()
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_call(self, async_client: AsyncGboxClient) -> None:
+ async with async_client.v1.models.with_streaming_response.call(
+ action={
+ "type": "click",
+ "target": "the VSCode app icon on the bottom dock",
+ },
+ screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ model = await response.parse()
+ assert_matches_type(ModelCallResponse, model, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
From 81bd4ad176524c933d50f44f47a20eba6f883f50 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 22 Oct 2025 07:38:51 +0000
Subject: [PATCH 11/31] feat(api): api update
---
.stats.yml | 4 +-
src/gbox_sdk/resources/v1/models.py | 4 +-
src/gbox_sdk/types/v1/model_call_params.py | 40 ++++++++-
src/gbox_sdk/types/v1/model_call_response.py | 95 +++++++++++++++++++-
tests/api_resources/v1/test_models.py | 16 ++--
5 files changed, 142 insertions(+), 17 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index c4d74cd4..25b5c638 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 93
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-94f652aed9166bb5cc03ef1a61a01a58537d1005c6c8af981cb5f0d2d60f0042.yml
-openapi_spec_hash: 9b1f6fd60387247c33622af8ad4687ab
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-98d437c98328821b44cd8e6870f4864262b4532205a559d47dc0cee4ca4167a3.yml
+openapi_spec_hash: 771bce49eefc11572feeec82009d5c33
config_hash: 09531a3b47b37eecfb541e383d99e62c
diff --git a/src/gbox_sdk/resources/v1/models.py b/src/gbox_sdk/resources/v1/models.py
index 19b111fc..b194343b 100644
--- a/src/gbox_sdk/resources/v1/models.py
+++ b/src/gbox_sdk/resources/v1/models.py
@@ -46,7 +46,7 @@ def with_streaming_response(self) -> ModelsResourceWithStreamingResponse:
def call(
self,
*,
- action: object,
+ action: model_call_params.Action,
screenshot: str,
model: Literal["gbox-handy-1"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -114,7 +114,7 @@ def with_streaming_response(self) -> AsyncModelsResourceWithStreamingResponse:
async def call(
self,
*,
- action: object,
+ action: model_call_params.Action,
screenshot: str,
model: Literal["gbox-handy-1"] | Omit = omit,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
diff --git a/src/gbox_sdk/types/v1/model_call_params.py b/src/gbox_sdk/types/v1/model_call_params.py
index 532fb995..03ca1e21 100644
--- a/src/gbox_sdk/types/v1/model_call_params.py
+++ b/src/gbox_sdk/types/v1/model_call_params.py
@@ -2,13 +2,14 @@
from __future__ import annotations
-from typing_extensions import Literal, Required, TypedDict
+from typing import Union
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
-__all__ = ["ModelCallParams"]
+__all__ = ["ModelCallParams", "Action", "ActionClickAction", "ActionDragAction", "ActionScrollAction"]
class ModelCallParams(TypedDict, total=False):
- action: Required[object]
+ action: Required[Action]
"""Structured action object (click or drag)"""
screenshot: Required[str]
@@ -16,3 +17,36 @@ class ModelCallParams(TypedDict, total=False):
model: Literal["gbox-handy-1"]
"""Model to use"""
+
+
+class ActionClickAction(TypedDict, total=False):
+ target: Required[str]
+ """Natural language description of what to click"""
+
+ type: Required[Literal["click", "drag", "scroll"]]
+ """Action type"""
+
+
+class ActionDragAction(TypedDict, total=False):
+ destination: Required[str]
+ """Natural language description of ending position"""
+
+ target: Required[str]
+ """Natural language description of starting position"""
+
+ type: Required[Literal["click", "drag", "scroll"]]
+ """Action type"""
+
+
+class ActionScrollAction(TypedDict, total=False):
+ direction: Required[Literal["up", "down", "left", "right"]]
+ """Scroll direction"""
+
+ location: Required[str]
+ """Natural language description of the location where the scroll should originate."""
+
+ type: Required[Literal["click", "drag", "scroll"]]
+ """Action type"""
+
+
+Action: TypeAlias = Union[ActionClickAction, ActionDragAction, ActionScrollAction]
diff --git a/src/gbox_sdk/types/v1/model_call_response.py b/src/gbox_sdk/types/v1/model_call_response.py
index ec219a06..c598f835 100644
--- a/src/gbox_sdk/types/v1/model_call_response.py
+++ b/src/gbox_sdk/types/v1/model_call_response.py
@@ -1,13 +1,104 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+from typing import Union
+from typing_extensions import Literal, TypeAlias
+
+from pydantic import Field as FieldInfo
+
from ..._models import BaseModel
-__all__ = ["ModelCallResponse"]
+__all__ = [
+ "ModelCallResponse",
+ "Response",
+ "ResponseModelClickResponseData",
+ "ResponseModelClickResponseDataCoordinates",
+ "ResponseModelDragResponseData",
+ "ResponseModelDragResponseDataCoordinates",
+ "ResponseModelDragResponseDataCoordinatesDestination",
+ "ResponseModelDragResponseDataCoordinatesTarget",
+ "ResponseModelScrollResponseData",
+ "ResponseModelScrollResponseDataCoordinates",
+]
+
+
+class ResponseModelClickResponseDataCoordinates(BaseModel):
+ x: float
+ """X coordinate. Returns -1 if no valid target is found."""
+
+ y: float
+ """Y coordinate. Returns -1 if no valid target is found."""
+
+
+class ResponseModelClickResponseData(BaseModel):
+ coordinates: ResponseModelClickResponseDataCoordinates
+ """Single click result with coordinates"""
+
+ type: Literal["click", "drag", "scroll"]
+ """Action type"""
+
+
+class ResponseModelDragResponseDataCoordinatesDestination(BaseModel):
+ x: float
+ """X coordinate. Returns -1 if no valid target is found."""
+
+ y: float
+ """Y coordinate. Returns -1 if no valid target is found."""
+
+
+class ResponseModelDragResponseDataCoordinatesTarget(BaseModel):
+ x: float
+ """X coordinate. Returns -1 if no valid target is found."""
+
+ y: float
+ """Y coordinate. Returns -1 if no valid target is found."""
+
+
+class ResponseModelDragResponseDataCoordinates(BaseModel):
+ destination: ResponseModelDragResponseDataCoordinatesDestination
+ """X and Y coordinates. Returns -1, -1 if no valid target is found."""
+
+ target: ResponseModelDragResponseDataCoordinatesTarget
+ """X and Y coordinates. Returns -1, -1 if no valid target is found."""
+
+
+class ResponseModelDragResponseData(BaseModel):
+ coordinates: ResponseModelDragResponseDataCoordinates
+ """Single drag result with target and destination coordinates"""
+
+ type: Literal["click", "drag", "scroll"]
+ """Action type"""
+
+
+class ResponseModelScrollResponseDataCoordinates(BaseModel):
+ scroll_x: float = FieldInfo(alias="scrollX")
+ """Horizontal scroll amount"""
+
+ scroll_y: float = FieldInfo(alias="scrollY")
+ """Vertical scroll amount"""
+
+ x: float
+ """X coordinate"""
+
+ y: float
+ """Y coordinate"""
+
+
+class ResponseModelScrollResponseData(BaseModel):
+ coordinates: ResponseModelScrollResponseDataCoordinates
+ """Single scroll result with location and direction"""
+
+ type: Literal["click", "drag", "scroll"]
+ """Action type"""
+
+
+Response: TypeAlias = Union[
+ ResponseModelClickResponseData, ResponseModelDragResponseData, ResponseModelScrollResponseData
+]
class ModelCallResponse(BaseModel):
id: str
"""Unique ID of this request, can be used for issue reporting and feedback"""
- response: object
+ response: Response
"""Model response data"""
diff --git a/tests/api_resources/v1/test_models.py b/tests/api_resources/v1/test_models.py
index c2dcd9d4..fe689673 100644
--- a/tests/api_resources/v1/test_models.py
+++ b/tests/api_resources/v1/test_models.py
@@ -22,8 +22,8 @@ class TestModels:
def test_method_call(self, client: GboxClient) -> None:
model = client.v1.models.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
)
@@ -34,8 +34,8 @@ def test_method_call(self, client: GboxClient) -> None:
def test_method_call_with_all_params(self, client: GboxClient) -> None:
model = client.v1.models.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
model="gbox-handy-1",
@@ -47,8 +47,8 @@ def test_method_call_with_all_params(self, client: GboxClient) -> None:
def test_raw_response_call(self, client: GboxClient) -> None:
response = client.v1.models.with_raw_response.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
)
@@ -63,8 +63,8 @@ def test_raw_response_call(self, client: GboxClient) -> None:
def test_streaming_response_call(self, client: GboxClient) -> None:
with client.v1.models.with_streaming_response.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
) as response:
@@ -87,8 +87,8 @@ class TestAsyncModels:
async def test_method_call(self, async_client: AsyncGboxClient) -> None:
model = await async_client.v1.models.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
)
@@ -99,8 +99,8 @@ async def test_method_call(self, async_client: AsyncGboxClient) -> None:
async def test_method_call_with_all_params(self, async_client: AsyncGboxClient) -> None:
model = await async_client.v1.models.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
model="gbox-handy-1",
@@ -112,8 +112,8 @@ async def test_method_call_with_all_params(self, async_client: AsyncGboxClient)
async def test_raw_response_call(self, async_client: AsyncGboxClient) -> None:
response = await async_client.v1.models.with_raw_response.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
)
@@ -128,8 +128,8 @@ async def test_raw_response_call(self, async_client: AsyncGboxClient) -> None:
async def test_streaming_response_call(self, async_client: AsyncGboxClient) -> None:
async with async_client.v1.models.with_streaming_response.call(
action={
- "type": "click",
"target": "the VSCode app icon on the bottom dock",
+ "type": "click",
},
screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
) as response:
From 6305ee55127a17c94429dcb281358e4f0327310a Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 23 Oct 2025 06:56:43 +0000
Subject: [PATCH 12/31] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 25b5c638..22f1d4ea 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 93
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-98d437c98328821b44cd8e6870f4864262b4532205a559d47dc0cee4ca4167a3.yml
-openapi_spec_hash: 771bce49eefc11572feeec82009d5c33
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-73ade1a6172c4edeed17bfe00172f2ce4895f0ed719095811ddf7aa98f223e9d.yml
+openapi_spec_hash: 54afd675feb947f0096fc8b215525a98
config_hash: 09531a3b47b37eecfb541e383d99e62c
From 7d0d335f15219ee0262bb615821ba975fbec5902 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 24 Oct 2025 02:04:36 +0000
Subject: [PATCH 13/31] feat(api): api update
---
.stats.yml | 4 ++--
src/gbox_sdk/resources/v1/models.py | 10 ++++++++--
src/gbox_sdk/types/v1/model_call_params.py | 8 +++++++-
tests/api_resources/v1/test_models.py | 16 ++++++++--------
4 files changed, 25 insertions(+), 13 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 22f1d4ea..50703640 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 93
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-73ade1a6172c4edeed17bfe00172f2ce4895f0ed719095811ddf7aa98f223e9d.yml
-openapi_spec_hash: 54afd675feb947f0096fc8b215525a98
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-f635173eacab4cd113f0f0d93fbe7c54cfc8878da547f2569fd2347deac8d1eb.yml
+openapi_spec_hash: 45881a4a8bcb3ee80eba3b99148e4edc
config_hash: 09531a3b47b37eecfb541e383d99e62c
diff --git a/src/gbox_sdk/resources/v1/models.py b/src/gbox_sdk/resources/v1/models.py
index b194343b..f956416a 100644
--- a/src/gbox_sdk/resources/v1/models.py
+++ b/src/gbox_sdk/resources/v1/models.py
@@ -62,7 +62,10 @@ def call(
Args:
action: Structured action object (click or drag)
- screenshot: HTTP(S) URL to screenshot image
+ screenshot: Screenshot image as HTTP(S) URL or base64-encoded data URI. Supports both
+ formats: 1) HTTP(S) URL pointing to an image file; 2) Base64-encoded data URI
+ with format 'data:image/png;base64,[data]' or 'data:image/jpeg;base64,[data]'.
+ Only PNG and JPEG formats are supported for base64.
model: Model to use
@@ -130,7 +133,10 @@ async def call(
Args:
action: Structured action object (click or drag)
- screenshot: HTTP(S) URL to screenshot image
+ screenshot: Screenshot image as HTTP(S) URL or base64-encoded data URI. Supports both
+ formats: 1) HTTP(S) URL pointing to an image file; 2) Base64-encoded data URI
+ with format 'data:image/png;base64,[data]' or 'data:image/jpeg;base64,[data]'.
+ Only PNG and JPEG formats are supported for base64.
model: Model to use
diff --git a/src/gbox_sdk/types/v1/model_call_params.py b/src/gbox_sdk/types/v1/model_call_params.py
index 03ca1e21..2011e5c7 100644
--- a/src/gbox_sdk/types/v1/model_call_params.py
+++ b/src/gbox_sdk/types/v1/model_call_params.py
@@ -13,7 +13,13 @@ class ModelCallParams(TypedDict, total=False):
"""Structured action object (click or drag)"""
screenshot: Required[str]
- """HTTP(S) URL to screenshot image"""
+ """Screenshot image as HTTP(S) URL or base64-encoded data URI.
+
+ Supports both formats: 1) HTTP(S) URL pointing to an image file; 2)
+ Base64-encoded data URI with format 'data:image/png;base64,[data]' or
+ 'data:image/jpeg;base64,[data]'. Only PNG and JPEG formats are supported for
+ base64.
+ """
model: Literal["gbox-handy-1"]
"""Model to use"""
diff --git a/tests/api_resources/v1/test_models.py b/tests/api_resources/v1/test_models.py
index fe689673..99c17297 100644
--- a/tests/api_resources/v1/test_models.py
+++ b/tests/api_resources/v1/test_models.py
@@ -25,7 +25,7 @@ def test_method_call(self, client: GboxClient) -> None:
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
)
assert_matches_type(ModelCallResponse, model, path=["response"])
@@ -37,7 +37,7 @@ def test_method_call_with_all_params(self, client: GboxClient) -> None:
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
model="gbox-handy-1",
)
assert_matches_type(ModelCallResponse, model, path=["response"])
@@ -50,7 +50,7 @@ def test_raw_response_call(self, client: GboxClient) -> None:
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
)
assert response.is_closed is True
@@ -66,7 +66,7 @@ def test_streaming_response_call(self, client: GboxClient) -> None:
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -90,7 +90,7 @@ async def test_method_call(self, async_client: AsyncGboxClient) -> None:
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
)
assert_matches_type(ModelCallResponse, model, path=["response"])
@@ -102,7 +102,7 @@ async def test_method_call_with_all_params(self, async_client: AsyncGboxClient)
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
model="gbox-handy-1",
)
assert_matches_type(ModelCallResponse, model, path=["response"])
@@ -115,7 +115,7 @@ async def test_raw_response_call(self, async_client: AsyncGboxClient) -> None:
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
)
assert response.is_closed is True
@@ -131,7 +131,7 @@ async def test_streaming_response_call(self, async_client: AsyncGboxClient) -> N
"target": "the VSCode app icon on the bottom dock",
"type": "click",
},
- screenshot="https://gru-activate2-public-assets.s3.us-west-2.amazonaws.com/jessica/screenshot-1759332945616-pu0ovj.png",
+ screenshot="screenshot",
) as response:
assert not response.is_closed
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
From b04c21353a0b0a68ccb2f932801c4a77b6554063 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 27 Oct 2025 02:02:06 +0000
Subject: [PATCH 14/31] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index 50703640..19c5516d 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 93
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-f635173eacab4cd113f0f0d93fbe7c54cfc8878da547f2569fd2347deac8d1eb.yml
openapi_spec_hash: 45881a4a8bcb3ee80eba3b99148e4edc
-config_hash: 09531a3b47b37eecfb541e383d99e62c
+config_hash: 81d3b919894888941a53f0c0f5645d33
From df6c78477153dcf98418a2c65273c2ffc68033e4 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 28 Oct 2025 00:50:57 +0000
Subject: [PATCH 15/31] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index 19c5516d..8e82f91f 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 93
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-f635173eacab4cd113f0f0d93fbe7c54cfc8878da547f2569fd2347deac8d1eb.yml
openapi_spec_hash: 45881a4a8bcb3ee80eba3b99148e4edc
-config_hash: 81d3b919894888941a53f0c0f5645d33
+config_hash: 150e072da248093344a304f500748675
From 12bd9ce7b5e266cc7791697c8d707b9a962b1b68 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 30 Oct 2025 02:09:49 +0000
Subject: [PATCH 16/31] fix(client): close streams without requiring full
consumption
---
src/gbox_sdk/_streaming.py | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/gbox_sdk/_streaming.py b/src/gbox_sdk/_streaming.py
index 41efee78..05f67073 100644
--- a/src/gbox_sdk/_streaming.py
+++ b/src/gbox_sdk/_streaming.py
@@ -57,9 +57,8 @@ def __stream__(self) -> Iterator[_T]:
for sse in iterator:
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
- # Ensure the entire stream is consumed
- for _sse in iterator:
- ...
+ # As we might not fully consume the response stream, we need to close it explicitly
+ response.close()
def __enter__(self) -> Self:
return self
@@ -121,9 +120,8 @@ async def __stream__(self) -> AsyncIterator[_T]:
async for sse in iterator:
yield process_data(data=sse.json(), cast_to=cast_to, response=response)
- # Ensure the entire stream is consumed
- async for _sse in iterator:
- ...
+ # As we might not fully consume the response stream, we need to close it explicitly
+ await response.aclose()
async def __aenter__(self) -> Self:
return self
From 0178cf3a021f94850ec707e63f759cd24836128b Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 31 Oct 2025 02:20:33 +0000
Subject: [PATCH 17/31] chore(internal/tests): avoid race condition with
implicit client cleanup
---
tests/test_client.py | 368 ++++++++++++++++++++++++-------------------
1 file changed, 204 insertions(+), 164 deletions(-)
diff --git a/tests/test_client.py b/tests/test_client.py
index 2a422ab4..0f7f830e 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -59,51 +59,49 @@ def _get_open_connections(client: GboxClient | AsyncGboxClient) -> int:
class TestGboxClient:
- client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
-
@pytest.mark.respx(base_url=base_url)
- def test_raw_response(self, respx_mock: MockRouter) -> None:
+ def test_raw_response(self, respx_mock: MockRouter, client: GboxClient) -> None:
respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = self.client.post("/foo", cast_to=httpx.Response)
+ response = client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
assert response.json() == {"foo": "bar"}
@pytest.mark.respx(base_url=base_url)
- def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
+ def test_raw_response_for_binary(self, respx_mock: MockRouter, client: GboxClient) -> None:
respx_mock.post("/foo").mock(
return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
)
- response = self.client.post("/foo", cast_to=httpx.Response)
+ response = client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
assert response.json() == {"foo": "bar"}
- def test_copy(self) -> None:
- copied = self.client.copy()
- assert id(copied) != id(self.client)
+ def test_copy(self, client: GboxClient) -> None:
+ copied = client.copy()
+ assert id(copied) != id(client)
- copied = self.client.copy(api_key="another My API Key")
+ copied = client.copy(api_key="another My API Key")
assert copied.api_key == "another My API Key"
- assert self.client.api_key == "My API Key"
+ assert client.api_key == "My API Key"
- def test_copy_default_options(self) -> None:
+ def test_copy_default_options(self, client: GboxClient) -> None:
# options that have a default are overridden correctly
- copied = self.client.copy(max_retries=7)
+ copied = client.copy(max_retries=7)
assert copied.max_retries == 7
- assert self.client.max_retries == 0
+ assert client.max_retries == 0
copied2 = copied.copy(max_retries=6)
assert copied2.max_retries == 6
assert copied.max_retries == 7
# timeout
- assert isinstance(self.client.timeout, httpx.Timeout)
- copied = self.client.copy(timeout=None)
+ assert isinstance(client.timeout, httpx.Timeout)
+ copied = client.copy(timeout=None)
assert copied.timeout is None
- assert isinstance(self.client.timeout, httpx.Timeout)
+ assert isinstance(client.timeout, httpx.Timeout)
def test_copy_default_headers(self) -> None:
client = GboxClient(
@@ -138,6 +136,7 @@ def test_copy_default_headers(self) -> None:
match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
):
client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+ client.close()
def test_copy_default_query(self) -> None:
client = GboxClient(
@@ -175,13 +174,15 @@ def test_copy_default_query(self) -> None:
):
client.copy(set_default_query={}, default_query={"foo": "Bar"})
- def test_copy_signature(self) -> None:
+ client.close()
+
+ def test_copy_signature(self, client: GboxClient) -> None:
# ensure the same parameters that can be passed to the client are defined in the `.copy()` method
init_signature = inspect.signature(
# mypy doesn't like that we access the `__init__` property.
- self.client.__init__, # type: ignore[misc]
+ client.__init__, # type: ignore[misc]
)
- copy_signature = inspect.signature(self.client.copy)
+ copy_signature = inspect.signature(client.copy)
exclude_params = {"transport", "proxies", "_strict_response_validation"}
for name in init_signature.parameters.keys():
@@ -192,12 +193,12 @@ def test_copy_signature(self) -> None:
assert copy_param is not None, f"copy() signature is missing the {name} param"
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
- def test_copy_build_request(self) -> None:
+ def test_copy_build_request(self, client: GboxClient) -> None:
options = FinalRequestOptions(method="get", url="/foo")
def build_request(options: FinalRequestOptions) -> None:
- client = self.client.copy()
- client._build_request(options)
+ client_copy = client.copy()
+ client_copy._build_request(options)
# ensure that the machinery is warmed up before tracing starts.
build_request(options)
@@ -254,14 +255,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic
print(frame)
raise AssertionError()
- def test_request_timeout(self) -> None:
- request = self.client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ def test_request_timeout(self, client: GboxClient) -> None:
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
- request = self.client._build_request(
- FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
- )
+ request = client._build_request(FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0)))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(100.0)
@@ -274,6 +273,8 @@ def test_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(0)
+ client.close()
+
def test_http_client_timeout_option(self) -> None:
# custom timeout given to the httpx client should be used
with httpx.Client(timeout=None) as http_client:
@@ -285,6 +286,8 @@ def test_http_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(None)
+ client.close()
+
# no timeout given to the httpx client should not use the httpx default
with httpx.Client() as http_client:
client = GboxClient(
@@ -295,6 +298,8 @@ def test_http_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
+ client.close()
+
# explicitly passing the default timeout currently results in it being ignored
with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
client = GboxClient(
@@ -305,6 +310,8 @@ def test_http_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT # our default
+ client.close()
+
async def test_invalid_http_client(self) -> None:
with pytest.raises(TypeError, match="Invalid `http_client` arg"):
async with httpx.AsyncClient() as http_client:
@@ -316,14 +323,14 @@ async def test_invalid_http_client(self) -> None:
)
def test_default_headers_option(self) -> None:
- client = GboxClient(
+ test_client = GboxClient(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
- request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
assert request.headers.get("x-stainless-lang") == "python"
- client2 = GboxClient(
+ test_client2 = GboxClient(
base_url=base_url,
api_key=api_key,
_strict_response_validation=True,
@@ -332,10 +339,13 @@ def test_default_headers_option(self) -> None:
"X-Stainless-Lang": "my-overriding-header",
},
)
- request = client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+ request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "stainless"
assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+ test_client.close()
+ test_client2.close()
+
def test_validate_headers(self) -> None:
client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -364,8 +374,10 @@ def test_default_query_option(self) -> None:
url = httpx.URL(request.url)
assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
- def test_request_extra_json(self) -> None:
- request = self.client._build_request(
+ client.close()
+
+ def test_request_extra_json(self, client: GboxClient) -> None:
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -376,7 +388,7 @@ def test_request_extra_json(self) -> None:
data = json.loads(request.content.decode("utf-8"))
assert data == {"foo": "bar", "baz": False}
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -387,7 +399,7 @@ def test_request_extra_json(self) -> None:
assert data == {"baz": False}
# `extra_json` takes priority over `json_data` when keys clash
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -398,8 +410,8 @@ def test_request_extra_json(self) -> None:
data = json.loads(request.content.decode("utf-8"))
assert data == {"foo": "bar", "baz": None}
- def test_request_extra_headers(self) -> None:
- request = self.client._build_request(
+ def test_request_extra_headers(self, client: GboxClient) -> None:
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -409,7 +421,7 @@ def test_request_extra_headers(self) -> None:
assert request.headers.get("X-Foo") == "Foo"
# `extra_headers` takes priority over `default_headers` when keys clash
- request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request(
+ request = client.with_options(default_headers={"X-Bar": "true"})._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -420,8 +432,8 @@ def test_request_extra_headers(self) -> None:
)
assert request.headers.get("X-Bar") == "false"
- def test_request_extra_query(self) -> None:
- request = self.client._build_request(
+ def test_request_extra_query(self, client: GboxClient) -> None:
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -434,7 +446,7 @@ def test_request_extra_query(self) -> None:
assert params == {"my_query_param": "Foo"}
# if both `query` and `extra_query` are given, they are merged
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -448,7 +460,7 @@ def test_request_extra_query(self) -> None:
assert params == {"bar": "1", "foo": "2"}
# `extra_query` takes priority over `query` when keys clash
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -491,7 +503,7 @@ def test_multipart_repeating_array(self, client: GboxClient) -> None:
]
@pytest.mark.respx(base_url=base_url)
- def test_basic_union_response(self, respx_mock: MockRouter) -> None:
+ def test_basic_union_response(self, respx_mock: MockRouter, client: GboxClient) -> None:
class Model1(BaseModel):
name: str
@@ -500,12 +512,12 @@ class Model2(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
@pytest.mark.respx(base_url=base_url)
- def test_union_response_different_types(self, respx_mock: MockRouter) -> None:
+ def test_union_response_different_types(self, respx_mock: MockRouter, client: GboxClient) -> None:
"""Union of objects with the same field name using a different type"""
class Model1(BaseModel):
@@ -516,18 +528,18 @@ class Model2(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
- response = self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ response = client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model1)
assert response.foo == 1
@pytest.mark.respx(base_url=base_url)
- def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None:
+ def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: GboxClient) -> None:
"""
Response that sets Content-Type to something other than application/json but returns json data
"""
@@ -543,7 +555,7 @@ class Model(BaseModel):
)
)
- response = self.client.get("/foo", cast_to=Model)
+ response = client.get("/foo", cast_to=Model)
assert isinstance(response, Model)
assert response.foo == 2
@@ -555,6 +567,8 @@ def test_base_url_setter(self) -> None:
assert client.base_url == "https://example.com/from_setter/"
+ client.close()
+
def test_base_url_env(self) -> None:
with update_env(GBOX_CLIENT_BASE_URL="http://localhost:5000/from/env"):
client = GboxClient(api_key=api_key, _strict_response_validation=True)
@@ -570,6 +584,8 @@ def test_base_url_env(self) -> None:
)
assert str(client.base_url).startswith("https://gbox.ai/api/v1/")
+ client.close()
+
@pytest.mark.parametrize(
"client",
[
@@ -594,6 +610,7 @@ def test_base_url_trailing_slash(self, client: GboxClient) -> None:
),
)
assert request.url == "http://localhost:5000/custom/path/foo"
+ client.close()
@pytest.mark.parametrize(
"client",
@@ -619,6 +636,7 @@ def test_base_url_no_trailing_slash(self, client: GboxClient) -> None:
),
)
assert request.url == "http://localhost:5000/custom/path/foo"
+ client.close()
@pytest.mark.parametrize(
"client",
@@ -644,35 +662,36 @@ def test_absolute_request_url(self, client: GboxClient) -> None:
),
)
assert request.url == "https://myapi.com/foo"
+ client.close()
def test_copied_client_does_not_close_http(self) -> None:
- client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
- assert not client.is_closed()
+ test_client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
+ assert not test_client.is_closed()
- copied = client.copy()
- assert copied is not client
+ copied = test_client.copy()
+ assert copied is not test_client
del copied
- assert not client.is_closed()
+ assert not test_client.is_closed()
def test_client_context_manager(self) -> None:
- client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
- with client as c2:
- assert c2 is client
+ test_client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
+ with test_client as c2:
+ assert c2 is test_client
assert not c2.is_closed()
- assert not client.is_closed()
- assert client.is_closed()
+ assert not test_client.is_closed()
+ assert test_client.is_closed()
@pytest.mark.respx(base_url=base_url)
- def test_client_response_validation_error(self, respx_mock: MockRouter) -> None:
+ def test_client_response_validation_error(self, respx_mock: MockRouter, client: GboxClient) -> None:
class Model(BaseModel):
foo: str
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
with pytest.raises(APIResponseValidationError) as exc:
- self.client.get("/foo", cast_to=Model)
+ client.get("/foo", cast_to=Model)
assert isinstance(exc.value.__cause__, ValidationError)
@@ -694,11 +713,14 @@ class Model(BaseModel):
with pytest.raises(APIResponseValidationError):
strict_client.get("/foo", cast_to=Model)
- client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=False)
+ non_strict_client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=False)
- response = client.get("/foo", cast_to=Model)
+ response = non_strict_client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]
+ strict_client.close()
+ non_strict_client.close()
+
@pytest.mark.parametrize(
"remaining_retries,retry_after,timeout",
[
@@ -721,9 +743,9 @@ class Model(BaseModel):
],
)
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
- def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
- client = GboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
-
+ def test_parse_retry_after_header(
+ self, remaining_retries: int, retry_after: str, timeout: float, client: GboxClient
+ ) -> None:
headers = httpx.Headers({"retry-after": retry_after})
options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
@@ -737,7 +759,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien
with pytest.raises(APITimeoutError):
client.v1.boxes.with_streaming_response.create_android().__enter__()
- assert _get_open_connections(self.client) == 0
+ assert _get_open_connections(client) == 0
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
@@ -746,7 +768,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client
with pytest.raises(APIStatusError):
client.v1.boxes.with_streaming_response.create_android().__enter__()
- assert _get_open_connections(self.client) == 0
+ assert _get_open_connections(client) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@@ -848,83 +870,77 @@ def test_default_client_creation(self) -> None:
)
@pytest.mark.respx(base_url=base_url)
- def test_follow_redirects(self, respx_mock: MockRouter) -> None:
+ def test_follow_redirects(self, respx_mock: MockRouter, client: GboxClient) -> None:
# Test that the default follow_redirects=True allows following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
- response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+ response = client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
assert response.status_code == 200
assert response.json() == {"status": "ok"}
@pytest.mark.respx(base_url=base_url)
- def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
+ def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: GboxClient) -> None:
# Test that follow_redirects=False prevents following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
with pytest.raises(APIStatusError) as exc_info:
- self.client.post(
- "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
- )
+ client.post("/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response)
assert exc_info.value.response.status_code == 302
assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"
class TestAsyncGboxClient:
- client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
-
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
- async def test_raw_response(self, respx_mock: MockRouter) -> None:
+ async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncGboxClient) -> None:
respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = await self.client.post("/foo", cast_to=httpx.Response)
+ response = await async_client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
assert response.json() == {"foo": "bar"}
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
- async def test_raw_response_for_binary(self, respx_mock: MockRouter) -> None:
+ async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncGboxClient) -> None:
respx_mock.post("/foo").mock(
return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}')
)
- response = await self.client.post("/foo", cast_to=httpx.Response)
+ response = await async_client.post("/foo", cast_to=httpx.Response)
assert response.status_code == 200
assert isinstance(response, httpx.Response)
assert response.json() == {"foo": "bar"}
- def test_copy(self) -> None:
- copied = self.client.copy()
- assert id(copied) != id(self.client)
+ def test_copy(self, async_client: AsyncGboxClient) -> None:
+ copied = async_client.copy()
+ assert id(copied) != id(async_client)
- copied = self.client.copy(api_key="another My API Key")
+ copied = async_client.copy(api_key="another My API Key")
assert copied.api_key == "another My API Key"
- assert self.client.api_key == "My API Key"
+ assert async_client.api_key == "My API Key"
- def test_copy_default_options(self) -> None:
+ def test_copy_default_options(self, async_client: AsyncGboxClient) -> None:
# options that have a default are overridden correctly
- copied = self.client.copy(max_retries=7)
+ copied = async_client.copy(max_retries=7)
assert copied.max_retries == 7
- assert self.client.max_retries == 0
+ assert async_client.max_retries == 0
copied2 = copied.copy(max_retries=6)
assert copied2.max_retries == 6
assert copied.max_retries == 7
# timeout
- assert isinstance(self.client.timeout, httpx.Timeout)
- copied = self.client.copy(timeout=None)
+ assert isinstance(async_client.timeout, httpx.Timeout)
+ copied = async_client.copy(timeout=None)
assert copied.timeout is None
- assert isinstance(self.client.timeout, httpx.Timeout)
+ assert isinstance(async_client.timeout, httpx.Timeout)
- def test_copy_default_headers(self) -> None:
+ async def test_copy_default_headers(self) -> None:
client = AsyncGboxClient(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
@@ -957,8 +973,9 @@ def test_copy_default_headers(self) -> None:
match="`default_headers` and `set_default_headers` arguments are mutually exclusive",
):
client.copy(set_default_headers={}, default_headers={"X-Foo": "Bar"})
+ await client.close()
- def test_copy_default_query(self) -> None:
+ async def test_copy_default_query(self) -> None:
client = AsyncGboxClient(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"}
)
@@ -994,13 +1011,15 @@ def test_copy_default_query(self) -> None:
):
client.copy(set_default_query={}, default_query={"foo": "Bar"})
- def test_copy_signature(self) -> None:
+ await client.close()
+
+ def test_copy_signature(self, async_client: AsyncGboxClient) -> None:
# ensure the same parameters that can be passed to the client are defined in the `.copy()` method
init_signature = inspect.signature(
# mypy doesn't like that we access the `__init__` property.
- self.client.__init__, # type: ignore[misc]
+ async_client.__init__, # type: ignore[misc]
)
- copy_signature = inspect.signature(self.client.copy)
+ copy_signature = inspect.signature(async_client.copy)
exclude_params = {"transport", "proxies", "_strict_response_validation"}
for name in init_signature.parameters.keys():
@@ -1011,12 +1030,12 @@ def test_copy_signature(self) -> None:
assert copy_param is not None, f"copy() signature is missing the {name} param"
@pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12")
- def test_copy_build_request(self) -> None:
+ def test_copy_build_request(self, async_client: AsyncGboxClient) -> None:
options = FinalRequestOptions(method="get", url="/foo")
def build_request(options: FinalRequestOptions) -> None:
- client = self.client.copy()
- client._build_request(options)
+ client_copy = async_client.copy()
+ client_copy._build_request(options)
# ensure that the machinery is warmed up before tracing starts.
build_request(options)
@@ -1073,12 +1092,12 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic
print(frame)
raise AssertionError()
- async def test_request_timeout(self) -> None:
- request = self.client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ async def test_request_timeout(self, async_client: AsyncGboxClient) -> None:
+ request = async_client._build_request(FinalRequestOptions(method="get", url="/foo"))
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
- request = self.client._build_request(
+ request = async_client._build_request(
FinalRequestOptions(method="get", url="/foo", timeout=httpx.Timeout(100.0))
)
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
@@ -1093,6 +1112,8 @@ async def test_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(0)
+ await client.close()
+
async def test_http_client_timeout_option(self) -> None:
# custom timeout given to the httpx client should be used
async with httpx.AsyncClient(timeout=None) as http_client:
@@ -1104,6 +1125,8 @@ async def test_http_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == httpx.Timeout(None)
+ await client.close()
+
# no timeout given to the httpx client should not use the httpx default
async with httpx.AsyncClient() as http_client:
client = AsyncGboxClient(
@@ -1114,6 +1137,8 @@ async def test_http_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT
+ await client.close()
+
# explicitly passing the default timeout currently results in it being ignored
async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client:
client = AsyncGboxClient(
@@ -1124,6 +1149,8 @@ async def test_http_client_timeout_option(self) -> None:
timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore
assert timeout == DEFAULT_TIMEOUT # our default
+ await client.close()
+
def test_invalid_http_client(self) -> None:
with pytest.raises(TypeError, match="Invalid `http_client` arg"):
with httpx.Client() as http_client:
@@ -1134,15 +1161,15 @@ def test_invalid_http_client(self) -> None:
http_client=cast(Any, http_client),
)
- def test_default_headers_option(self) -> None:
- client = AsyncGboxClient(
+ async def test_default_headers_option(self) -> None:
+ test_client = AsyncGboxClient(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"}
)
- request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
+ request = test_client._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "bar"
assert request.headers.get("x-stainless-lang") == "python"
- client2 = AsyncGboxClient(
+ test_client2 = AsyncGboxClient(
base_url=base_url,
api_key=api_key,
_strict_response_validation=True,
@@ -1151,10 +1178,13 @@ def test_default_headers_option(self) -> None:
"X-Stainless-Lang": "my-overriding-header",
},
)
- request = client2._build_request(FinalRequestOptions(method="get", url="/foo"))
+ request = test_client2._build_request(FinalRequestOptions(method="get", url="/foo"))
assert request.headers.get("x-foo") == "stainless"
assert request.headers.get("x-stainless-lang") == "my-overriding-header"
+ await test_client.close()
+ await test_client2.close()
+
def test_validate_headers(self) -> None:
client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
request = client._build_request(FinalRequestOptions(method="get", url="/foo"))
@@ -1165,7 +1195,7 @@ def test_validate_headers(self) -> None:
client2 = AsyncGboxClient(base_url=base_url, api_key=None, _strict_response_validation=True)
_ = client2
- def test_default_query_option(self) -> None:
+ async def test_default_query_option(self) -> None:
client = AsyncGboxClient(
base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"}
)
@@ -1183,8 +1213,10 @@ def test_default_query_option(self) -> None:
url = httpx.URL(request.url)
assert dict(url.params) == {"foo": "baz", "query_param": "overridden"}
- def test_request_extra_json(self) -> None:
- request = self.client._build_request(
+ await client.close()
+
+ def test_request_extra_json(self, client: GboxClient) -> None:
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1195,7 +1227,7 @@ def test_request_extra_json(self) -> None:
data = json.loads(request.content.decode("utf-8"))
assert data == {"foo": "bar", "baz": False}
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1206,7 +1238,7 @@ def test_request_extra_json(self) -> None:
assert data == {"baz": False}
# `extra_json` takes priority over `json_data` when keys clash
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1217,8 +1249,8 @@ def test_request_extra_json(self) -> None:
data = json.loads(request.content.decode("utf-8"))
assert data == {"foo": "bar", "baz": None}
- def test_request_extra_headers(self) -> None:
- request = self.client._build_request(
+ def test_request_extra_headers(self, client: GboxClient) -> None:
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1228,7 +1260,7 @@ def test_request_extra_headers(self) -> None:
assert request.headers.get("X-Foo") == "Foo"
# `extra_headers` takes priority over `default_headers` when keys clash
- request = self.client.with_options(default_headers={"X-Bar": "true"})._build_request(
+ request = client.with_options(default_headers={"X-Bar": "true"})._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1239,8 +1271,8 @@ def test_request_extra_headers(self) -> None:
)
assert request.headers.get("X-Bar") == "false"
- def test_request_extra_query(self) -> None:
- request = self.client._build_request(
+ def test_request_extra_query(self, client: GboxClient) -> None:
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1253,7 +1285,7 @@ def test_request_extra_query(self) -> None:
assert params == {"my_query_param": "Foo"}
# if both `query` and `extra_query` are given, they are merged
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1267,7 +1299,7 @@ def test_request_extra_query(self) -> None:
assert params == {"bar": "1", "foo": "2"}
# `extra_query` takes priority over `query` when keys clash
- request = self.client._build_request(
+ request = client._build_request(
FinalRequestOptions(
method="post",
url="/foo",
@@ -1310,7 +1342,7 @@ def test_multipart_repeating_array(self, async_client: AsyncGboxClient) -> None:
]
@pytest.mark.respx(base_url=base_url)
- async def test_basic_union_response(self, respx_mock: MockRouter) -> None:
+ async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncGboxClient) -> None:
class Model1(BaseModel):
name: str
@@ -1319,12 +1351,12 @@ class Model2(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
@pytest.mark.respx(base_url=base_url)
- async def test_union_response_different_types(self, respx_mock: MockRouter) -> None:
+ async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncGboxClient) -> None:
"""Union of objects with the same field name using a different type"""
class Model1(BaseModel):
@@ -1335,18 +1367,20 @@ class Model2(BaseModel):
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
- response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model2)
assert response.foo == "bar"
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": 1}))
- response = await self.client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
+ response = await async_client.get("/foo", cast_to=cast(Any, Union[Model1, Model2]))
assert isinstance(response, Model1)
assert response.foo == 1
@pytest.mark.respx(base_url=base_url)
- async def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter) -> None:
+ async def test_non_application_json_content_type_for_json_data(
+ self, respx_mock: MockRouter, async_client: AsyncGboxClient
+ ) -> None:
"""
Response that sets Content-Type to something other than application/json but returns json data
"""
@@ -1362,11 +1396,11 @@ class Model(BaseModel):
)
)
- response = await self.client.get("/foo", cast_to=Model)
+ response = await async_client.get("/foo", cast_to=Model)
assert isinstance(response, Model)
assert response.foo == 2
- def test_base_url_setter(self) -> None:
+ async def test_base_url_setter(self) -> None:
client = AsyncGboxClient(
base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True
)
@@ -1376,7 +1410,9 @@ def test_base_url_setter(self) -> None:
assert client.base_url == "https://example.com/from_setter/"
- def test_base_url_env(self) -> None:
+ await client.close()
+
+ async def test_base_url_env(self) -> None:
with update_env(GBOX_CLIENT_BASE_URL="http://localhost:5000/from/env"):
client = AsyncGboxClient(api_key=api_key, _strict_response_validation=True)
assert client.base_url == "http://localhost:5000/from/env/"
@@ -1391,6 +1427,8 @@ def test_base_url_env(self) -> None:
)
assert str(client.base_url).startswith("https://gbox.ai/api/v1/")
+ await client.close()
+
@pytest.mark.parametrize(
"client",
[
@@ -1406,7 +1444,7 @@ def test_base_url_env(self) -> None:
],
ids=["standard", "custom http client"],
)
- def test_base_url_trailing_slash(self, client: AsyncGboxClient) -> None:
+ async def test_base_url_trailing_slash(self, client: AsyncGboxClient) -> None:
request = client._build_request(
FinalRequestOptions(
method="post",
@@ -1415,6 +1453,7 @@ def test_base_url_trailing_slash(self, client: AsyncGboxClient) -> None:
),
)
assert request.url == "http://localhost:5000/custom/path/foo"
+ await client.close()
@pytest.mark.parametrize(
"client",
@@ -1431,7 +1470,7 @@ def test_base_url_trailing_slash(self, client: AsyncGboxClient) -> None:
],
ids=["standard", "custom http client"],
)
- def test_base_url_no_trailing_slash(self, client: AsyncGboxClient) -> None:
+ async def test_base_url_no_trailing_slash(self, client: AsyncGboxClient) -> None:
request = client._build_request(
FinalRequestOptions(
method="post",
@@ -1440,6 +1479,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncGboxClient) -> None:
),
)
assert request.url == "http://localhost:5000/custom/path/foo"
+ await client.close()
@pytest.mark.parametrize(
"client",
@@ -1456,7 +1496,7 @@ def test_base_url_no_trailing_slash(self, client: AsyncGboxClient) -> None:
],
ids=["standard", "custom http client"],
)
- def test_absolute_request_url(self, client: AsyncGboxClient) -> None:
+ async def test_absolute_request_url(self, client: AsyncGboxClient) -> None:
request = client._build_request(
FinalRequestOptions(
method="post",
@@ -1465,37 +1505,39 @@ def test_absolute_request_url(self, client: AsyncGboxClient) -> None:
),
)
assert request.url == "https://myapi.com/foo"
+ await client.close()
async def test_copied_client_does_not_close_http(self) -> None:
- client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
- assert not client.is_closed()
+ test_client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
+ assert not test_client.is_closed()
- copied = client.copy()
- assert copied is not client
+ copied = test_client.copy()
+ assert copied is not test_client
del copied
await asyncio.sleep(0.2)
- assert not client.is_closed()
+ assert not test_client.is_closed()
async def test_client_context_manager(self) -> None:
- client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
- async with client as c2:
- assert c2 is client
+ test_client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
+ async with test_client as c2:
+ assert c2 is test_client
assert not c2.is_closed()
- assert not client.is_closed()
- assert client.is_closed()
+ assert not test_client.is_closed()
+ assert test_client.is_closed()
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
- async def test_client_response_validation_error(self, respx_mock: MockRouter) -> None:
+ async def test_client_response_validation_error(
+ self, respx_mock: MockRouter, async_client: AsyncGboxClient
+ ) -> None:
class Model(BaseModel):
foo: str
respx_mock.get("/foo").mock(return_value=httpx.Response(200, json={"foo": {"invalid": True}}))
with pytest.raises(APIResponseValidationError) as exc:
- await self.client.get("/foo", cast_to=Model)
+ await async_client.get("/foo", cast_to=Model)
assert isinstance(exc.value.__cause__, ValidationError)
@@ -1506,7 +1548,6 @@ async def test_client_max_retries_validation(self) -> None:
)
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
async def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None:
class Model(BaseModel):
name: str
@@ -1518,11 +1559,14 @@ class Model(BaseModel):
with pytest.raises(APIResponseValidationError):
await strict_client.get("/foo", cast_to=Model)
- client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=False)
+ non_strict_client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=False)
- response = await client.get("/foo", cast_to=Model)
+ response = await non_strict_client.get("/foo", cast_to=Model)
assert isinstance(response, str) # type: ignore[unreachable]
+ await strict_client.close()
+ await non_strict_client.close()
+
@pytest.mark.parametrize(
"remaining_retries,retry_after,timeout",
[
@@ -1545,13 +1589,12 @@ class Model(BaseModel):
],
)
@mock.patch("time.time", mock.MagicMock(return_value=1696004797))
- @pytest.mark.asyncio
- async def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str, timeout: float) -> None:
- client = AsyncGboxClient(base_url=base_url, api_key=api_key, _strict_response_validation=True)
-
+ async def test_parse_retry_after_header(
+ self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncGboxClient
+ ) -> None:
headers = httpx.Headers({"retry-after": retry_after})
options = FinalRequestOptions(method="get", url="/foo", max_retries=3)
- calculated = client._calculate_retry_timeout(remaining_retries, options, headers)
+ calculated = async_client._calculate_retry_timeout(remaining_retries, options, headers)
assert calculated == pytest.approx(timeout, 1 * 0.875) # pyright: ignore[reportUnknownMemberType]
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@@ -1564,7 +1607,7 @@ async def test_retrying_timeout_errors_doesnt_leak(
with pytest.raises(APITimeoutError):
await async_client.v1.boxes.with_streaming_response.create_android().__aenter__()
- assert _get_open_connections(self.client) == 0
+ assert _get_open_connections(async_client) == 0
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
@@ -1575,12 +1618,11 @@ async def test_retrying_status_errors_doesnt_leak(
with pytest.raises(APIStatusError):
await async_client.v1.boxes.with_streaming_response.create_android().__aenter__()
- assert _get_open_connections(self.client) == 0
+ assert _get_open_connections(async_client) == 0
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
@pytest.mark.parametrize("failure_mode", ["status", "exception"])
async def test_retries_taken(
self,
@@ -1612,7 +1654,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
async def test_omit_retry_count_header(
self, async_client: AsyncGboxClient, failures_before_success: int, respx_mock: MockRouter
) -> None:
@@ -1638,7 +1679,6 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
@pytest.mark.parametrize("failures_before_success", [0, 2, 4])
@mock.patch("gbox_sdk._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout)
@pytest.mark.respx(base_url=base_url)
- @pytest.mark.asyncio
async def test_overwrite_retry_count_header(
self, async_client: AsyncGboxClient, failures_before_success: int, respx_mock: MockRouter
) -> None:
@@ -1688,26 +1728,26 @@ async def test_default_client_creation(self) -> None:
)
@pytest.mark.respx(base_url=base_url)
- async def test_follow_redirects(self, respx_mock: MockRouter) -> None:
+ async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncGboxClient) -> None:
# Test that the default follow_redirects=True allows following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"}))
- response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
+ response = await async_client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response)
assert response.status_code == 200
assert response.json() == {"status": "ok"}
@pytest.mark.respx(base_url=base_url)
- async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None:
+ async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncGboxClient) -> None:
# Test that follow_redirects=False prevents following redirects
respx_mock.post("/redirect").mock(
return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"})
)
with pytest.raises(APIStatusError) as exc_info:
- await self.client.post(
+ await async_client.post(
"/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response
)
From b4164163611f49880e86e852395df0ebf7c4ce0e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 4 Nov 2025 02:53:39 +0000
Subject: [PATCH 18/31] feat(api): api update
---
.stats.yml | 6 +-
api.md | 21 +-
src/gbox_sdk/resources/v1/boxes/actions.py | 123 ++++----
src/gbox_sdk/types/v1/boxes/__init__.py | 7 +
.../types/v1/boxes/action_click_response.py | 76 +++++
.../types/v1/boxes/action_drag_response.py | 98 +++++++
.../v1/boxes/action_long_press_response.py | 73 +++++
.../types/v1/boxes/action_scroll_response.py | 72 +++++
.../types/v1/boxes/action_swipe_response.py | 98 +++++++
.../types/v1/boxes/action_tap_response.py | 66 +++++
.../types/v1/boxes/action_touch_response.py | 125 +++++++++
tests/api_resources/v1/boxes/test_actions.py | 263 +++++++++---------
12 files changed, 832 insertions(+), 196 deletions(-)
create mode 100644 src/gbox_sdk/types/v1/boxes/action_click_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/action_drag_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/action_long_press_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/action_scroll_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/action_swipe_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/action_tap_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/action_touch_response.py
diff --git a/.stats.yml b/.stats.yml
index 8e82f91f..b4b4cfd2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 93
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-f635173eacab4cd113f0f0d93fbe7c54cfc8878da547f2569fd2347deac8d1eb.yml
-openapi_spec_hash: 45881a4a8bcb3ee80eba3b99148e4edc
-config_hash: 150e072da248093344a304f500748675
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-34ade2db86f74473eedf7d351937f22a17d896fc59629d9a299d36505c676201.yml
+openapi_spec_hash: a7d3032b05d18db56f4708df08d49ae5
+config_hash: 81d3b919894888941a53f0c0f5645d33
diff --git a/api.md b/api.md
index c974c050..c797e020 100644
--- a/api.md
+++ b/api.md
@@ -89,28 +89,35 @@ from gbox_sdk.types.v1.boxes import (
ActionResult,
ActionScreenshotOptions,
DetectedElement,
+ ActionClickResponse,
ActionClipboardGetResponse,
+ ActionDragResponse,
ActionElementsDetectResponse,
ActionExtractResponse,
+ ActionLongPressResponse,
ActionRecordingStopResponse,
ActionRewindExtractResponse,
ActionScreenLayoutResponse,
ActionScreenshotResponse,
+ ActionScrollResponse,
ActionSettingsResponse,
ActionSettingsResetResponse,
ActionSettingsUpdateResponse,
+ ActionSwipeResponse,
+ ActionTapResponse,
+ ActionTouchResponse,
)
```
Methods:
-- client.v1.boxes.actions.click(box_id, \*\*params) -> ActionResult
+- client.v1.boxes.actions.click(box_id, \*\*params) -> ActionClickResponse
- client.v1.boxes.actions.clipboard_get(box_id) -> str
- client.v1.boxes.actions.clipboard_set(box_id, \*\*params) -> None
-- client.v1.boxes.actions.drag(box_id, \*\*params) -> ActionResult
+- client.v1.boxes.actions.drag(box_id, \*\*params) -> ActionDragResponse
- client.v1.boxes.actions.elements_detect(box_id, \*\*params) -> ActionElementsDetectResponse
- client.v1.boxes.actions.extract(box_id, \*\*params) -> ActionExtractResponse
-- client.v1.boxes.actions.long_press(box_id, \*\*params) -> ActionResult
+- client.v1.boxes.actions.long_press(box_id, \*\*params) -> ActionLongPressResponse
- client.v1.boxes.actions.move(box_id, \*\*params) -> ActionResult
- client.v1.boxes.actions.press_button(box_id, \*\*params) -> ActionResult
- client.v1.boxes.actions.press_key(box_id, \*\*params) -> ActionResult
@@ -122,13 +129,13 @@ Methods:
- client.v1.boxes.actions.screen_layout(box_id) -> ActionScreenLayoutResponse
- client.v1.boxes.actions.screen_rotation(box_id, \*\*params) -> ActionResult
- client.v1.boxes.actions.screenshot(box_id, \*\*params) -> ActionScreenshotResponse
-- client.v1.boxes.actions.scroll(box_id, \*\*params) -> ActionResult
+- client.v1.boxes.actions.scroll(box_id, \*\*params) -> ActionScrollResponse
- client.v1.boxes.actions.settings(box_id) -> ActionSettingsResponse
- client.v1.boxes.actions.settings_reset(box_id) -> ActionSettingsResetResponse
- client.v1.boxes.actions.settings_update(box_id, \*\*params) -> ActionSettingsUpdateResponse
-- client.v1.boxes.actions.swipe(box_id, \*\*params) -> ActionResult
-- client.v1.boxes.actions.tap(box_id, \*\*params) -> ActionResult
-- client.v1.boxes.actions.touch(box_id, \*\*params) -> ActionResult
+- client.v1.boxes.actions.swipe(box_id, \*\*params) -> ActionSwipeResponse
+- client.v1.boxes.actions.tap(box_id, \*\*params) -> ActionTapResponse
+- client.v1.boxes.actions.touch(box_id, \*\*params) -> ActionTouchResponse
- client.v1.boxes.actions.type(box_id, \*\*params) -> ActionResult
### Proxy
diff --git a/src/gbox_sdk/resources/v1/boxes/actions.py b/src/gbox_sdk/resources/v1/boxes/actions.py
index 4f350e67..ded60c32 100644
--- a/src/gbox_sdk/resources/v1/boxes/actions.py
+++ b/src/gbox_sdk/resources/v1/boxes/actions.py
@@ -39,9 +39,16 @@
action_settings_update_params,
)
from ....types.v1.boxes.action_result import ActionResult
+from ....types.v1.boxes.action_tap_response import ActionTapResponse
+from ....types.v1.boxes.action_drag_response import ActionDragResponse
+from ....types.v1.boxes.action_click_response import ActionClickResponse
+from ....types.v1.boxes.action_swipe_response import ActionSwipeResponse
+from ....types.v1.boxes.action_touch_response import ActionTouchResponse
+from ....types.v1.boxes.action_scroll_response import ActionScrollResponse
from ....types.v1.boxes.detected_element import DetectedElement
from ....types.v1.boxes.action_extract_response import ActionExtractResponse
from ....types.v1.boxes.action_settings_response import ActionSettingsResponse
+from ....types.v1.boxes.action_long_press_response import ActionLongPressResponse
from ....types.v1.boxes.action_screenshot_response import ActionScreenshotResponse
from ....types.v1.boxes.action_common_options_param import ActionCommonOptionsParam
from ....types.v1.boxes.action_screen_layout_response import ActionScreenLayoutResponse
@@ -94,7 +101,7 @@ def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
"""
Simulates a click action on the box
@@ -172,7 +179,7 @@ def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
"""
Simulates a click action on the box
@@ -249,7 +256,7 @@ def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
"""
Simulates a click action on the box
@@ -327,7 +334,7 @@ def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return self._post(
@@ -350,7 +357,7 @@ def click(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionClickResponse,
)
def clipboard_get(
@@ -443,7 +450,7 @@ def drag(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionDragResponse:
"""
Simulates a drag gesture, moving from a start point to an end point over a set
duration. Supports simple start/end coordinates, multi-point drag paths, and
@@ -523,7 +530,7 @@ def drag(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionDragResponse:
"""
Simulates a drag gesture, moving from a start point to an end point over a set
duration. Supports simple start/end coordinates, multi-point drag paths, and
@@ -603,7 +610,7 @@ def drag(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionDragResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return self._post(
@@ -625,7 +632,7 @@ def drag(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionDragResponse,
)
def elements_detect(
@@ -742,7 +749,7 @@ def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
"""
Perform a long press action at specified coordinates for a specified duration.
Useful for triggering context menus, drag operations, or other long-press
@@ -822,7 +829,7 @@ def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
"""
Perform a long press action at specified coordinates for a specified duration.
Useful for triggering context menus, drag operations, or other long-press
@@ -901,7 +908,7 @@ def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
"""
Perform a long press action at specified coordinates for a specified duration.
Useful for triggering context menus, drag operations, or other long-press
@@ -981,7 +988,7 @@ def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return self._post(
@@ -1003,7 +1010,7 @@ def long_press(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionLongPressResponse,
)
def move(
@@ -1791,7 +1798,7 @@ def scroll(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionScrollResponse:
"""Performs a scroll action.
Supports both advanced scroll with coordinates and
@@ -1875,7 +1882,7 @@ def scroll(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionScrollResponse:
"""Performs a scroll action.
Supports both advanced scroll with coordinates and
@@ -1970,7 +1977,7 @@ def scroll(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionScrollResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return self._post(
@@ -1996,7 +2003,7 @@ def scroll(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionScrollResponse,
)
def settings(
@@ -2131,7 +2138,7 @@ def swipe(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionSwipeResponse:
"""
Performs a swipe in the specified direction
@@ -2216,7 +2223,7 @@ def swipe(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionSwipeResponse:
"""
Performs a swipe in the specified direction
@@ -2298,7 +2305,7 @@ def swipe(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionSwipeResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return self._post(
@@ -2322,7 +2329,7 @@ def swipe(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionSwipeResponse,
)
@overload
@@ -2343,7 +2350,7 @@ def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
"""
Tap action for Android devices using ADB input tap command
@@ -2415,7 +2422,7 @@ def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
"""
Tap action for Android devices using ADB input tap command
@@ -2486,7 +2493,7 @@ def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
"""
Tap action for Android devices using ADB input tap command
@@ -2558,7 +2565,7 @@ def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return self._post(
@@ -2579,7 +2586,7 @@ def tap(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionTapResponse,
)
def touch(
@@ -2598,7 +2605,7 @@ def touch(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTouchResponse:
"""Performs more advanced touch gestures.
Use this endpoint to simulate realistic
@@ -2669,7 +2676,7 @@ def touch(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionTouchResponse,
)
def type(
@@ -2812,7 +2819,7 @@ async def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
"""
Simulates a click action on the box
@@ -2890,7 +2897,7 @@ async def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
"""
Simulates a click action on the box
@@ -2967,7 +2974,7 @@ async def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
"""
Simulates a click action on the box
@@ -3045,7 +3052,7 @@ async def click(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionClickResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return await self._post(
@@ -3068,7 +3075,7 @@ async def click(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionClickResponse,
)
async def clipboard_get(
@@ -3163,7 +3170,7 @@ async def drag(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionDragResponse:
"""
Simulates a drag gesture, moving from a start point to an end point over a set
duration. Supports simple start/end coordinates, multi-point drag paths, and
@@ -3243,7 +3250,7 @@ async def drag(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionDragResponse:
"""
Simulates a drag gesture, moving from a start point to an end point over a set
duration. Supports simple start/end coordinates, multi-point drag paths, and
@@ -3323,7 +3330,7 @@ async def drag(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionDragResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return await self._post(
@@ -3345,7 +3352,7 @@ async def drag(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionDragResponse,
)
async def elements_detect(
@@ -3464,7 +3471,7 @@ async def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
"""
Perform a long press action at specified coordinates for a specified duration.
Useful for triggering context menus, drag operations, or other long-press
@@ -3544,7 +3551,7 @@ async def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
"""
Perform a long press action at specified coordinates for a specified duration.
Useful for triggering context menus, drag operations, or other long-press
@@ -3623,7 +3630,7 @@ async def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
"""
Perform a long press action at specified coordinates for a specified duration.
Useful for triggering context menus, drag operations, or other long-press
@@ -3703,7 +3710,7 @@ async def long_press(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionLongPressResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return await self._post(
@@ -3725,7 +3732,7 @@ async def long_press(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionLongPressResponse,
)
async def move(
@@ -4515,7 +4522,7 @@ async def scroll(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionScrollResponse:
"""Performs a scroll action.
Supports both advanced scroll with coordinates and
@@ -4599,7 +4606,7 @@ async def scroll(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionScrollResponse:
"""Performs a scroll action.
Supports both advanced scroll with coordinates and
@@ -4694,7 +4701,7 @@ async def scroll(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionScrollResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return await self._post(
@@ -4720,7 +4727,7 @@ async def scroll(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionScrollResponse,
)
async def settings(
@@ -4857,7 +4864,7 @@ async def swipe(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionSwipeResponse:
"""
Performs a swipe in the specified direction
@@ -4942,7 +4949,7 @@ async def swipe(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionSwipeResponse:
"""
Performs a swipe in the specified direction
@@ -5024,7 +5031,7 @@ async def swipe(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionSwipeResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return await self._post(
@@ -5048,7 +5055,7 @@ async def swipe(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionSwipeResponse,
)
@overload
@@ -5069,7 +5076,7 @@ async def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
"""
Tap action for Android devices using ADB input tap command
@@ -5141,7 +5148,7 @@ async def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
"""
Tap action for Android devices using ADB input tap command
@@ -5212,7 +5219,7 @@ async def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
"""
Tap action for Android devices using ADB input tap command
@@ -5284,7 +5291,7 @@ async def tap(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTapResponse:
if not box_id:
raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
return await self._post(
@@ -5305,7 +5312,7 @@ async def tap(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionTapResponse,
)
async def touch(
@@ -5324,7 +5331,7 @@ async def touch(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionResult:
+ ) -> ActionTouchResponse:
"""Performs more advanced touch gestures.
Use this endpoint to simulate realistic
@@ -5395,7 +5402,7 @@ async def touch(
options=make_request_options(
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
),
- cast_to=ActionResult,
+ cast_to=ActionTouchResponse,
)
async def type(
diff --git a/src/gbox_sdk/types/v1/boxes/__init__.py b/src/gbox_sdk/types/v1/boxes/__init__.py
index d868dd23..3a04fb06 100644
--- a/src/gbox_sdk/types/v1/boxes/__init__.py
+++ b/src/gbox_sdk/types/v1/boxes/__init__.py
@@ -33,12 +33,18 @@
from .proxy_set_response import ProxySetResponse as ProxySetResponse
from .action_click_params import ActionClickParams as ActionClickParams
from .action_swipe_params import ActionSwipeParams as ActionSwipeParams
+from .action_tap_response import ActionTapResponse as ActionTapResponse
from .action_touch_params import ActionTouchParams as ActionTouchParams
from .android_open_params import AndroidOpenParams as AndroidOpenParams
from .browser_open_params import BrowserOpenParams as BrowserOpenParams
+from .action_drag_response import ActionDragResponse as ActionDragResponse
from .action_scroll_params import ActionScrollParams as ActionScrollParams
+from .action_click_response import ActionClickResponse as ActionClickResponse
from .action_extract_params import ActionExtractParams as ActionExtractParams
+from .action_swipe_response import ActionSwipeResponse as ActionSwipeResponse
+from .action_touch_response import ActionTouchResponse as ActionTouchResponse
from .browser_open_response import BrowserOpenResponse as BrowserOpenResponse
+from .action_scroll_response import ActionScrollResponse as ActionScrollResponse
from .android_install_params import AndroidInstallParams as AndroidInstallParams
from .android_restart_params import AndroidRestartParams as AndroidRestartParams
from .android_restore_params import AndroidRestoreParams as AndroidRestoreParams
@@ -65,6 +71,7 @@
from .media_create_album_params import MediaCreateAlbumParams as MediaCreateAlbumParams
from .media_list_media_response import MediaListMediaResponse as MediaListMediaResponse
from .media_update_album_params import MediaUpdateAlbumParams as MediaUpdateAlbumParams
+from .action_long_press_response import ActionLongPressResponse as ActionLongPressResponse
from .action_press_button_params import ActionPressButtonParams as ActionPressButtonParams
from .action_screenshot_response import ActionScreenshotResponse as ActionScreenshotResponse
from .browser_close_tab_response import BrowserCloseTabResponse as BrowserCloseTabResponse
diff --git a/src/gbox_sdk/types/v1/boxes/action_click_response.py b/src/gbox_sdk/types/v1/boxes/action_click_response.py
new file mode 100644
index 00000000..dbe5d7e3
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_click_response.py
@@ -0,0 +1,76 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["ActionClickResponse", "Actual", "Screenshot", "ScreenshotAfter", "ScreenshotBefore", "ScreenshotTrace"]
+
+
+class Actual(BaseModel):
+ button: Literal["left", "right", "middle"]
+ """Mouse button that was clicked"""
+
+ double: bool
+ """Whether a double click was performed"""
+
+ x: float
+ """X coordinate where the click was executed"""
+
+ y: float
+ """Y coordinate where the click was executed"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionClickResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """
+ Actual parameters used when executing the click action, with the same field
+ names as input parameters
+ """
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_drag_response.py b/src/gbox_sdk/types/v1/boxes/action_drag_response.py
new file mode 100644
index 00000000..f7e1a9b5
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_drag_response.py
@@ -0,0 +1,98 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "ActionDragResponse",
+ "Actual",
+ "ActualEnd",
+ "ActualStart",
+ "Screenshot",
+ "ScreenshotAfter",
+ "ScreenshotBefore",
+ "ScreenshotTrace",
+]
+
+
+class ActualEnd(BaseModel):
+ x: float
+ """X coordinate of a point in the drag path"""
+
+ y: float
+ """Y coordinate of a point in the drag path"""
+
+
+class ActualStart(BaseModel):
+ x: float
+ """X coordinate of a point in the drag path"""
+
+ y: float
+ """Y coordinate of a point in the drag path"""
+
+
+class Actual(BaseModel):
+ duration: str
+ """Duration of the drag
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 500ms
+ """
+
+ end: ActualEnd
+ """Single point in a drag path"""
+
+ start: ActualStart
+ """Single point in a drag path"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionDragResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """Actual parameters used when executing the drag action"""
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_long_press_response.py b/src/gbox_sdk/types/v1/boxes/action_long_press_response.py
new file mode 100644
index 00000000..a0da1743
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_long_press_response.py
@@ -0,0 +1,73 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["ActionLongPressResponse", "Actual", "Screenshot", "ScreenshotAfter", "ScreenshotBefore", "ScreenshotTrace"]
+
+
+class Actual(BaseModel):
+ duration: str
+ """Duration of the long press
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 1s
+ """
+
+ x: float
+ """X coordinate where the long press was executed"""
+
+ y: float
+ """Y coordinate where the long press was executed"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionLongPressResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """Actual parameters used when executing the long press action"""
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_scroll_response.py b/src/gbox_sdk/types/v1/boxes/action_scroll_response.py
new file mode 100644
index 00000000..c00bb3dc
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_scroll_response.py
@@ -0,0 +1,72 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["ActionScrollResponse", "Actual", "Screenshot", "ScreenshotAfter", "ScreenshotBefore", "ScreenshotTrace"]
+
+
+class Actual(BaseModel):
+ scroll_x: float = FieldInfo(alias="scrollX")
+ """Horizontal scroll amount"""
+
+ scroll_y: float = FieldInfo(alias="scrollY")
+ """Vertical scroll amount"""
+
+ x: float
+ """X coordinate of the scroll position"""
+
+ y: float
+ """Y coordinate of the scroll position"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionScrollResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """Actual parameters used when executing the scroll action"""
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_swipe_response.py b/src/gbox_sdk/types/v1/boxes/action_swipe_response.py
new file mode 100644
index 00000000..deaa77e9
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_swipe_response.py
@@ -0,0 +1,98 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "ActionSwipeResponse",
+ "Actual",
+ "ActualEnd",
+ "ActualStart",
+ "Screenshot",
+ "ScreenshotAfter",
+ "ScreenshotBefore",
+ "ScreenshotTrace",
+]
+
+
+class ActualEnd(BaseModel):
+ x: float
+ """Start/end x coordinate of the swipe path"""
+
+ y: float
+ """Start/end y coordinate of the swipe path"""
+
+
+class ActualStart(BaseModel):
+ x: float
+ """Start/end x coordinate of the swipe path"""
+
+ y: float
+ """Start/end y coordinate of the swipe path"""
+
+
+class Actual(BaseModel):
+ duration: str
+ """Duration of the swipe
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 500ms
+ """
+
+ end: ActualEnd
+ """Swipe path"""
+
+ start: ActualStart
+ """Swipe path"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionSwipeResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """Actual parameters used when executing the swipe action"""
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_tap_response.py b/src/gbox_sdk/types/v1/boxes/action_tap_response.py
new file mode 100644
index 00000000..270f26f7
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_tap_response.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Optional
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["ActionTapResponse", "Actual", "Screenshot", "ScreenshotAfter", "ScreenshotBefore", "ScreenshotTrace"]
+
+
+class Actual(BaseModel):
+ x: float
+ """X coordinate where the tap was executed"""
+
+ y: float
+ """Y coordinate where the tap was executed"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionTapResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """Actual parameters used when executing the tap action"""
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_touch_response.py b/src/gbox_sdk/types/v1/boxes/action_touch_response.py
new file mode 100644
index 00000000..a8c01ff5
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_touch_response.py
@@ -0,0 +1,125 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, Union, Optional
+from typing_extensions import TypeAlias
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = [
+ "ActionTouchResponse",
+ "Actual",
+ "ActualPoint",
+ "ActualPointStart",
+ "ActualPointAction",
+ "ActualPointActionTouchPointMoveAction",
+ "ActualPointActionTouchPointWaitAction",
+ "Screenshot",
+ "ScreenshotAfter",
+ "ScreenshotBefore",
+ "ScreenshotTrace",
+]
+
+
+class ActualPointStart(BaseModel):
+ x: float
+ """Starting X coordinate"""
+
+ y: float
+ """Starting Y coordinate"""
+
+
+class ActualPointActionTouchPointMoveAction(BaseModel):
+ duration: str
+ """Duration of the movement (e.g. "200ms")
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 200ms
+ """
+
+ type: str
+ """Type of the action"""
+
+ x: float
+ """Target X coordinate"""
+
+ y: float
+ """Target Y coordinate"""
+
+
+class ActualPointActionTouchPointWaitAction(BaseModel):
+ duration: str
+ """Duration to wait (e.g. "500ms")
+
+ Supported time units: ms (milliseconds), s (seconds), m (minutes), h (hours)
+ Example formats: "500ms", "30s", "5m", "1h" Default: 500ms
+ """
+
+ type: str
+ """Type of the action"""
+
+
+ActualPointAction: TypeAlias = Union[ActualPointActionTouchPointMoveAction, ActualPointActionTouchPointWaitAction]
+
+
+class ActualPoint(BaseModel):
+ start: ActualPointStart
+ """Initial touch point position"""
+
+ actions: Optional[List[ActualPointAction]] = None
+ """Sequence of actions to perform after initial touch"""
+
+
+class Actual(BaseModel):
+ points: List[ActualPoint]
+ """Array of touch points with their normalized coordinates and actions"""
+
+
+class ScreenshotAfter(BaseModel):
+ uri: str
+ """URI of the screenshot after the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotBefore(BaseModel):
+ uri: str
+ """URI of the screenshot before the action"""
+
+ presigned_url: Optional[str] = FieldInfo(alias="presignedUrl", default=None)
+ """Presigned url of the screenshot before the action"""
+
+
+class ScreenshotTrace(BaseModel):
+ uri: str
+ """URI of the screenshot with operation trace"""
+
+
+class Screenshot(BaseModel):
+ after: Optional[ScreenshotAfter] = None
+ """Screenshot taken after action execution"""
+
+ before: Optional[ScreenshotBefore] = None
+ """Screenshot taken before action execution"""
+
+ trace: Optional[ScreenshotTrace] = None
+ """Screenshot with action operation trace"""
+
+
+class ActionTouchResponse(BaseModel):
+ action_id: str = FieldInfo(alias="actionId")
+ """Unique identifier for each action.
+
+ Use this ID to locate the action and report issues.
+ """
+
+ actual: Actual
+ """Actual parameters used when executing the touch action"""
+
+ message: str
+ """message"""
+
+ screenshot: Optional[Screenshot] = None
+ """Complete screenshot result with operation trace, before and after images"""
diff --git a/tests/api_resources/v1/boxes/test_actions.py b/tests/api_resources/v1/boxes/test_actions.py
index 48a0888f..78067854 100644
--- a/tests/api_resources/v1/boxes/test_actions.py
+++ b/tests/api_resources/v1/boxes/test_actions.py
@@ -11,8 +11,15 @@
from tests.utils import assert_matches_type
from gbox_sdk.types.v1.boxes import (
ActionResult,
+ ActionTapResponse,
+ ActionDragResponse,
+ ActionClickResponse,
+ ActionSwipeResponse,
+ ActionTouchResponse,
+ ActionScrollResponse,
ActionExtractResponse,
ActionSettingsResponse,
+ ActionLongPressResponse,
ActionScreenshotResponse,
ActionScreenLayoutResponse,
ActionRecordingStopResponse,
@@ -37,7 +44,7 @@ def test_method_click_overload_1(self, client: GboxClient) -> None:
x=350,
y=250,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -62,7 +69,7 @@ def test_method_click_with_all_params_overload_1(self, client: GboxClient) -> No
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -76,7 +83,7 @@ def test_raw_response_click_overload_1(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -90,7 +97,7 @@ def test_streaming_response_click_overload_1(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -111,7 +118,7 @@ def test_method_click_overload_2(self, client: GboxClient) -> None:
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
target="login button",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -135,7 +142,7 @@ def test_method_click_with_all_params_overload_2(self, client: GboxClient) -> No
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -148,7 +155,7 @@ def test_raw_response_click_overload_2(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -161,7 +168,7 @@ def test_streaming_response_click_overload_2(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -196,7 +203,7 @@ def test_method_click_overload_3(self, client: GboxClient) -> None:
},
),
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -235,7 +242,7 @@ def test_method_click_with_all_params_overload_3(self, client: GboxClient) -> No
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -263,7 +270,7 @@ def test_raw_response_click_overload_3(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -291,7 +298,7 @@ def test_streaming_response_click_overload_3(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -421,7 +428,7 @@ def test_method_drag_overload_1(self, client: GboxClient) -> None:
"y": 150,
},
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -451,7 +458,7 @@ def test_method_drag_with_all_params_overload_1(self, client: GboxClient) -> Non
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -471,7 +478,7 @@ def test_raw_response_drag_overload_1(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -491,7 +498,7 @@ def test_streaming_response_drag_overload_1(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -531,7 +538,7 @@ def test_method_drag_overload_2(self, client: GboxClient) -> None:
},
],
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -567,7 +574,7 @@ def test_method_drag_with_all_params_overload_2(self, client: GboxClient) -> Non
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -593,7 +600,7 @@ def test_raw_response_drag_overload_2(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -619,7 +626,7 @@ def test_streaming_response_drag_overload_2(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -763,7 +770,7 @@ def test_method_long_press_overload_1(self, client: GboxClient) -> None:
x=350,
y=250,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -787,7 +794,7 @@ def test_method_long_press_with_all_params_overload_1(self, client: GboxClient)
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -801,7 +808,7 @@ def test_raw_response_long_press_overload_1(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -815,7 +822,7 @@ def test_streaming_response_long_press_overload_1(self, client: GboxClient) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -836,7 +843,7 @@ def test_method_long_press_overload_2(self, client: GboxClient) -> None:
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
target="Chrome icon",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -859,7 +866,7 @@ def test_method_long_press_with_all_params_overload_2(self, client: GboxClient)
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -872,7 +879,7 @@ def test_raw_response_long_press_overload_2(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -885,7 +892,7 @@ def test_streaming_response_long_press_overload_2(self, client: GboxClient) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -920,7 +927,7 @@ def test_method_long_press_overload_3(self, client: GboxClient) -> None:
},
),
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -958,7 +965,7 @@ def test_method_long_press_with_all_params_overload_3(self, client: GboxClient)
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -986,7 +993,7 @@ def test_raw_response_long_press_overload_3(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1014,7 +1021,7 @@ def test_streaming_response_long_press_overload_3(self, client: GboxClient) -> N
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1654,7 +1661,7 @@ def test_method_scroll_overload_1(self, client: GboxClient) -> None:
x=400,
y=300,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1679,7 +1686,7 @@ def test_method_scroll_with_all_params_overload_1(self, client: GboxClient) -> N
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1695,7 +1702,7 @@ def test_raw_response_scroll_overload_1(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1711,7 +1718,7 @@ def test_streaming_response_scroll_overload_1(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1734,7 +1741,7 @@ def test_method_scroll_overload_2(self, client: GboxClient) -> None:
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
direction="up",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1759,7 +1766,7 @@ def test_method_scroll_with_all_params_overload_2(self, client: GboxClient) -> N
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1772,7 +1779,7 @@ def test_raw_response_scroll_overload_2(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1785,7 +1792,7 @@ def test_streaming_response_scroll_overload_2(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -1935,7 +1942,7 @@ def test_method_swipe_overload_1(self, client: GboxClient) -> None:
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
direction="up",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1960,7 +1967,7 @@ def test_method_swipe_with_all_params_overload_1(self, client: GboxClient) -> No
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1973,7 +1980,7 @@ def test_raw_response_swipe_overload_1(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -1986,7 +1993,7 @@ def test_streaming_response_swipe_overload_1(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2013,7 +2020,7 @@ def test_method_swipe_overload_2(self, client: GboxClient) -> None:
"y": 150,
},
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2043,7 +2050,7 @@ def test_method_swipe_with_all_params_overload_2(self, client: GboxClient) -> No
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2063,7 +2070,7 @@ def test_raw_response_swipe_overload_2(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2083,7 +2090,7 @@ def test_streaming_response_swipe_overload_2(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2111,7 +2118,7 @@ def test_method_tap_overload_1(self, client: GboxClient) -> None:
x=350,
y=250,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2134,7 +2141,7 @@ def test_method_tap_with_all_params_overload_1(self, client: GboxClient) -> None
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2148,7 +2155,7 @@ def test_raw_response_tap_overload_1(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2162,7 +2169,7 @@ def test_streaming_response_tap_overload_1(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2183,7 +2190,7 @@ def test_method_tap_overload_2(self, client: GboxClient) -> None:
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
target="login button",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2205,7 +2212,7 @@ def test_method_tap_with_all_params_overload_2(self, client: GboxClient) -> None
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2218,7 +2225,7 @@ def test_raw_response_tap_overload_2(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2231,7 +2238,7 @@ def test_streaming_response_tap_overload_2(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2266,7 +2273,7 @@ def test_method_tap_overload_3(self, client: GboxClient) -> None:
},
),
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2303,7 +2310,7 @@ def test_method_tap_with_all_params_overload_3(self, client: GboxClient) -> None
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2331,7 +2338,7 @@ def test_raw_response_tap_overload_3(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2359,7 +2366,7 @@ def test_streaming_response_tap_overload_3(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2401,7 +2408,7 @@ def test_method_touch(self, client: GboxClient) -> None:
}
],
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2436,7 +2443,7 @@ def test_method_touch_with_all_params(self, client: GboxClient) -> None:
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2456,7 +2463,7 @@ def test_raw_response_touch(self, client: GboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2476,7 +2483,7 @@ def test_streaming_response_touch(self, client: GboxClient) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2580,7 +2587,7 @@ async def test_method_click_overload_1(self, async_client: AsyncGboxClient) -> N
x=350,
y=250,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2605,7 +2612,7 @@ async def test_method_click_with_all_params_overload_1(self, async_client: Async
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2619,7 +2626,7 @@ async def test_raw_response_click_overload_1(self, async_client: AsyncGboxClient
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2633,7 +2640,7 @@ async def test_streaming_response_click_overload_1(self, async_client: AsyncGbox
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2654,7 +2661,7 @@ async def test_method_click_overload_2(self, async_client: AsyncGboxClient) -> N
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
target="login button",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2678,7 +2685,7 @@ async def test_method_click_with_all_params_overload_2(self, async_client: Async
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2691,7 +2698,7 @@ async def test_raw_response_click_overload_2(self, async_client: AsyncGboxClient
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2704,7 +2711,7 @@ async def test_streaming_response_click_overload_2(self, async_client: AsyncGbox
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2739,7 +2746,7 @@ async def test_method_click_overload_3(self, async_client: AsyncGboxClient) -> N
},
),
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2778,7 +2785,7 @@ async def test_method_click_with_all_params_overload_3(self, async_client: Async
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2806,7 +2813,7 @@ async def test_raw_response_click_overload_3(self, async_client: AsyncGboxClient
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2834,7 +2841,7 @@ async def test_streaming_response_click_overload_3(self, async_client: AsyncGbox
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionClickResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -2964,7 +2971,7 @@ async def test_method_drag_overload_1(self, async_client: AsyncGboxClient) -> No
"y": 150,
},
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -2994,7 +3001,7 @@ async def test_method_drag_with_all_params_overload_1(self, async_client: AsyncG
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3014,7 +3021,7 @@ async def test_raw_response_drag_overload_1(self, async_client: AsyncGboxClient)
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3034,7 +3041,7 @@ async def test_streaming_response_drag_overload_1(self, async_client: AsyncGboxC
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -3074,7 +3081,7 @@ async def test_method_drag_overload_2(self, async_client: AsyncGboxClient) -> No
},
],
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3110,7 +3117,7 @@ async def test_method_drag_with_all_params_overload_2(self, async_client: AsyncG
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3136,7 +3143,7 @@ async def test_raw_response_drag_overload_2(self, async_client: AsyncGboxClient)
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3162,7 +3169,7 @@ async def test_streaming_response_drag_overload_2(self, async_client: AsyncGboxC
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionDragResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -3306,7 +3313,7 @@ async def test_method_long_press_overload_1(self, async_client: AsyncGboxClient)
x=350,
y=250,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3330,7 +3337,7 @@ async def test_method_long_press_with_all_params_overload_1(self, async_client:
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3344,7 +3351,7 @@ async def test_raw_response_long_press_overload_1(self, async_client: AsyncGboxC
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3358,7 +3365,7 @@ async def test_streaming_response_long_press_overload_1(self, async_client: Asyn
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -3379,7 +3386,7 @@ async def test_method_long_press_overload_2(self, async_client: AsyncGboxClient)
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
target="Chrome icon",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3402,7 +3409,7 @@ async def test_method_long_press_with_all_params_overload_2(self, async_client:
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3415,7 +3422,7 @@ async def test_raw_response_long_press_overload_2(self, async_client: AsyncGboxC
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3428,7 +3435,7 @@ async def test_streaming_response_long_press_overload_2(self, async_client: Asyn
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -3463,7 +3470,7 @@ async def test_method_long_press_overload_3(self, async_client: AsyncGboxClient)
},
),
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3501,7 +3508,7 @@ async def test_method_long_press_with_all_params_overload_3(self, async_client:
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3529,7 +3536,7 @@ async def test_raw_response_long_press_overload_3(self, async_client: AsyncGboxC
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -3557,7 +3564,7 @@ async def test_streaming_response_long_press_overload_3(self, async_client: Asyn
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionLongPressResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4197,7 +4204,7 @@ async def test_method_scroll_overload_1(self, async_client: AsyncGboxClient) ->
x=400,
y=300,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4222,7 +4229,7 @@ async def test_method_scroll_with_all_params_overload_1(self, async_client: Asyn
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4238,7 +4245,7 @@ async def test_raw_response_scroll_overload_1(self, async_client: AsyncGboxClien
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4254,7 +4261,7 @@ async def test_streaming_response_scroll_overload_1(self, async_client: AsyncGbo
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4277,7 +4284,7 @@ async def test_method_scroll_overload_2(self, async_client: AsyncGboxClient) ->
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
direction="up",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4302,7 +4309,7 @@ async def test_method_scroll_with_all_params_overload_2(self, async_client: Asyn
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4315,7 +4322,7 @@ async def test_raw_response_scroll_overload_2(self, async_client: AsyncGboxClien
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4328,7 +4335,7 @@ async def test_streaming_response_scroll_overload_2(self, async_client: AsyncGbo
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionScrollResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4478,7 +4485,7 @@ async def test_method_swipe_overload_1(self, async_client: AsyncGboxClient) -> N
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
direction="up",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4503,7 +4510,7 @@ async def test_method_swipe_with_all_params_overload_1(self, async_client: Async
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4516,7 +4523,7 @@ async def test_raw_response_swipe_overload_1(self, async_client: AsyncGboxClient
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4529,7 +4536,7 @@ async def test_streaming_response_swipe_overload_1(self, async_client: AsyncGbox
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4556,7 +4563,7 @@ async def test_method_swipe_overload_2(self, async_client: AsyncGboxClient) -> N
"y": 150,
},
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4586,7 +4593,7 @@ async def test_method_swipe_with_all_params_overload_2(self, async_client: Async
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4606,7 +4613,7 @@ async def test_raw_response_swipe_overload_2(self, async_client: AsyncGboxClient
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4626,7 +4633,7 @@ async def test_streaming_response_swipe_overload_2(self, async_client: AsyncGbox
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionSwipeResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4654,7 +4661,7 @@ async def test_method_tap_overload_1(self, async_client: AsyncGboxClient) -> Non
x=350,
y=250,
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4677,7 +4684,7 @@ async def test_method_tap_with_all_params_overload_1(self, async_client: AsyncGb
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4691,7 +4698,7 @@ async def test_raw_response_tap_overload_1(self, async_client: AsyncGboxClient)
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4705,7 +4712,7 @@ async def test_streaming_response_tap_overload_1(self, async_client: AsyncGboxCl
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4726,7 +4733,7 @@ async def test_method_tap_overload_2(self, async_client: AsyncGboxClient) -> Non
box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
target="login button",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4748,7 +4755,7 @@ async def test_method_tap_with_all_params_overload_2(self, async_client: AsyncGb
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4761,7 +4768,7 @@ async def test_raw_response_tap_overload_2(self, async_client: AsyncGboxClient)
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4774,7 +4781,7 @@ async def test_streaming_response_tap_overload_2(self, async_client: AsyncGboxCl
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4809,7 +4816,7 @@ async def test_method_tap_overload_3(self, async_client: AsyncGboxClient) -> Non
},
),
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4846,7 +4853,7 @@ async def test_method_tap_with_all_params_overload_3(self, async_client: AsyncGb
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4874,7 +4881,7 @@ async def test_raw_response_tap_overload_3(self, async_client: AsyncGboxClient)
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4902,7 +4909,7 @@ async def test_streaming_response_tap_overload_3(self, async_client: AsyncGboxCl
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTapResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -4944,7 +4951,7 @@ async def test_method_touch(self, async_client: AsyncGboxClient) -> None:
}
],
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4979,7 +4986,7 @@ async def test_method_touch_with_all_params(self, async_client: AsyncGboxClient)
presigned_expires_in="30m",
screenshot_delay="500ms",
)
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -4999,7 +5006,7 @@ async def test_raw_response_touch(self, async_client: AsyncGboxClient) -> None:
assert response.is_closed is True
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -5019,7 +5026,7 @@ async def test_streaming_response_touch(self, async_client: AsyncGboxClient) ->
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
action = await response.parse()
- assert_matches_type(ActionResult, action, path=["response"])
+ assert_matches_type(ActionTouchResponse, action, path=["response"])
assert cast(Any, response.is_closed) is True
From 57b312cf2f7abfb5f2d38838c072ce30d178b375 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 4 Nov 2025 03:27:18 +0000
Subject: [PATCH 19/31] chore(internal): grammar fix (it's -> its)
---
src/gbox_sdk/_utils/_utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/gbox_sdk/_utils/_utils.py b/src/gbox_sdk/_utils/_utils.py
index 50d59269..eec7f4a1 100644
--- a/src/gbox_sdk/_utils/_utils.py
+++ b/src/gbox_sdk/_utils/_utils.py
@@ -133,7 +133,7 @@ def is_given(obj: _T | NotGiven | Omit) -> TypeGuard[_T]:
# Type safe methods for narrowing types with TypeVars.
# The default narrowing for isinstance(obj, dict) is dict[unknown, unknown],
# however this cause Pyright to rightfully report errors. As we know we don't
-# care about the contained types we can safely use `object` in it's place.
+# care about the contained types we can safely use `object` in its place.
#
# There are two separate functions defined, `is_*` and `is_*_t` for different use cases.
# `is_*` is for when you're dealing with an unknown input
From df5876ed05231ee5b90ccff7bf2851897d974a8e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 5 Nov 2025 08:45:58 +0000
Subject: [PATCH 20/31] feat(api): api update
---
.stats.yml | 8 +-
api.md | 13 +
src/gbox_sdk/resources/v1/boxes/__init__.py | 14 ++
src/gbox_sdk/resources/v1/boxes/boxes.py | 32 +++
src/gbox_sdk/resources/v1/boxes/snapshot.py | 228 ++++++++++++++++++
src/gbox_sdk/types/v1/boxes/__init__.py | 3 +
.../types/v1/boxes/snapshot_create_params.py | 12 +
.../v1/boxes/snapshot_create_response.py | 23 ++
.../types/v1/boxes/snapshot_list_response.py | 38 +++
tests/api_resources/v1/boxes/test_snapshot.py | 172 +++++++++++++
10 files changed, 539 insertions(+), 4 deletions(-)
create mode 100644 src/gbox_sdk/resources/v1/boxes/snapshot.py
create mode 100644 src/gbox_sdk/types/v1/boxes/snapshot_create_params.py
create mode 100644 src/gbox_sdk/types/v1/boxes/snapshot_create_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
create mode 100644 tests/api_resources/v1/boxes/test_snapshot.py
diff --git a/.stats.yml b/.stats.yml
index b4b4cfd2..456c5d11 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 93
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-34ade2db86f74473eedf7d351937f22a17d896fc59629d9a299d36505c676201.yml
-openapi_spec_hash: a7d3032b05d18db56f4708df08d49ae5
-config_hash: 81d3b919894888941a53f0c0f5645d33
+configured_endpoints: 95
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-8846b7e7a8888e676190723800257c36b52a7fc2df16b65587bcf61620739df3.yml
+openapi_spec_hash: e7b0e7cf189e0583fa3bd4e935054853
+config_hash: c034f0250abf5ce12e31af4c24a03756
diff --git a/api.md b/api.md
index c797e020..c8c5efde 100644
--- a/api.md
+++ b/api.md
@@ -138,6 +138,19 @@ Methods:
- client.v1.boxes.actions.touch(box_id, \*\*params) -> ActionTouchResponse
- client.v1.boxes.actions.type(box_id, \*\*params) -> ActionResult
+### Snapshot
+
+Types:
+
+```python
+from gbox_sdk.types.v1.boxes import SnapshotCreateResponse, SnapshotListResponse
+```
+
+Methods:
+
+- client.v1.boxes.snapshot.create(box_id, \*\*params) -> SnapshotCreateResponse
+- client.v1.boxes.snapshot.list() -> SnapshotListResponse
+
### Proxy
Types:
diff --git a/src/gbox_sdk/resources/v1/boxes/__init__.py b/src/gbox_sdk/resources/v1/boxes/__init__.py
index e9f3297e..37768c5c 100644
--- a/src/gbox_sdk/resources/v1/boxes/__init__.py
+++ b/src/gbox_sdk/resources/v1/boxes/__init__.py
@@ -64,6 +64,14 @@
StorageResourceWithStreamingResponse,
AsyncStorageResourceWithStreamingResponse,
)
+from .snapshot import (
+ SnapshotResource,
+ AsyncSnapshotResource,
+ SnapshotResourceWithRawResponse,
+ AsyncSnapshotResourceWithRawResponse,
+ SnapshotResourceWithStreamingResponse,
+ AsyncSnapshotResourceWithStreamingResponse,
+)
__all__ = [
"StorageResource",
@@ -78,6 +86,12 @@
"AsyncActionsResourceWithRawResponse",
"ActionsResourceWithStreamingResponse",
"AsyncActionsResourceWithStreamingResponse",
+ "SnapshotResource",
+ "AsyncSnapshotResource",
+ "SnapshotResourceWithRawResponse",
+ "AsyncSnapshotResourceWithRawResponse",
+ "SnapshotResourceWithStreamingResponse",
+ "AsyncSnapshotResourceWithStreamingResponse",
"ProxyResource",
"AsyncProxyResource",
"ProxyResourceWithRawResponse",
diff --git a/src/gbox_sdk/resources/v1/boxes/boxes.py b/src/gbox_sdk/resources/v1/boxes/boxes.py
index 62d3c7c1..f037c4ee 100644
--- a/src/gbox_sdk/resources/v1/boxes/boxes.py
+++ b/src/gbox_sdk/resources/v1/boxes/boxes.py
@@ -63,6 +63,14 @@
StorageResourceWithStreamingResponse,
AsyncStorageResourceWithStreamingResponse,
)
+from .snapshot import (
+ SnapshotResource,
+ AsyncSnapshotResource,
+ SnapshotResourceWithRawResponse,
+ AsyncSnapshotResourceWithRawResponse,
+ SnapshotResourceWithStreamingResponse,
+ AsyncSnapshotResourceWithStreamingResponse,
+)
from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, SequenceNotStr, omit, not_given
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
@@ -113,6 +121,10 @@ def storage(self) -> StorageResource:
def actions(self) -> ActionsResource:
return ActionsResource(self._client)
+ @cached_property
+ def snapshot(self) -> SnapshotResource:
+ return SnapshotResource(self._client)
+
@cached_property
def proxy(self) -> ProxyResource:
return ProxyResource(self._client)
@@ -838,6 +850,10 @@ def storage(self) -> AsyncStorageResource:
def actions(self) -> AsyncActionsResource:
return AsyncActionsResource(self._client)
+ @cached_property
+ def snapshot(self) -> AsyncSnapshotResource:
+ return AsyncSnapshotResource(self._client)
+
@cached_property
def proxy(self) -> AsyncProxyResource:
return AsyncProxyResource(self._client)
@@ -1611,6 +1627,10 @@ def storage(self) -> StorageResourceWithRawResponse:
def actions(self) -> ActionsResourceWithRawResponse:
return ActionsResourceWithRawResponse(self._boxes.actions)
+ @cached_property
+ def snapshot(self) -> SnapshotResourceWithRawResponse:
+ return SnapshotResourceWithRawResponse(self._boxes.snapshot)
+
@cached_property
def proxy(self) -> ProxyResourceWithRawResponse:
return ProxyResourceWithRawResponse(self._boxes.proxy)
@@ -1687,6 +1707,10 @@ def storage(self) -> AsyncStorageResourceWithRawResponse:
def actions(self) -> AsyncActionsResourceWithRawResponse:
return AsyncActionsResourceWithRawResponse(self._boxes.actions)
+ @cached_property
+ def snapshot(self) -> AsyncSnapshotResourceWithRawResponse:
+ return AsyncSnapshotResourceWithRawResponse(self._boxes.snapshot)
+
@cached_property
def proxy(self) -> AsyncProxyResourceWithRawResponse:
return AsyncProxyResourceWithRawResponse(self._boxes.proxy)
@@ -1763,6 +1787,10 @@ def storage(self) -> StorageResourceWithStreamingResponse:
def actions(self) -> ActionsResourceWithStreamingResponse:
return ActionsResourceWithStreamingResponse(self._boxes.actions)
+ @cached_property
+ def snapshot(self) -> SnapshotResourceWithStreamingResponse:
+ return SnapshotResourceWithStreamingResponse(self._boxes.snapshot)
+
@cached_property
def proxy(self) -> ProxyResourceWithStreamingResponse:
return ProxyResourceWithStreamingResponse(self._boxes.proxy)
@@ -1839,6 +1867,10 @@ def storage(self) -> AsyncStorageResourceWithStreamingResponse:
def actions(self) -> AsyncActionsResourceWithStreamingResponse:
return AsyncActionsResourceWithStreamingResponse(self._boxes.actions)
+ @cached_property
+ def snapshot(self) -> AsyncSnapshotResourceWithStreamingResponse:
+ return AsyncSnapshotResourceWithStreamingResponse(self._boxes.snapshot)
+
@cached_property
def proxy(self) -> AsyncProxyResourceWithStreamingResponse:
return AsyncProxyResourceWithStreamingResponse(self._boxes.proxy)
diff --git a/src/gbox_sdk/resources/v1/boxes/snapshot.py b/src/gbox_sdk/resources/v1/boxes/snapshot.py
new file mode 100644
index 00000000..470bef79
--- /dev/null
+++ b/src/gbox_sdk/resources/v1/boxes/snapshot.py
@@ -0,0 +1,228 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+from ...._types import Body, Query, Headers, NotGiven, not_given
+from ...._utils import maybe_transform, async_maybe_transform
+from ...._compat import cached_property
+from ...._resource import SyncAPIResource, AsyncAPIResource
+from ...._response import (
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+)
+from ...._base_client import make_request_options
+from ....types.v1.boxes import snapshot_create_params
+from ....types.v1.boxes.snapshot_list_response import SnapshotListResponse
+from ....types.v1.boxes.snapshot_create_response import SnapshotCreateResponse
+
+__all__ = ["SnapshotResource", "AsyncSnapshotResource"]
+
+
+class SnapshotResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> SnapshotResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#accessing-raw-response-data-eg-headers
+ """
+ return SnapshotResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> SnapshotResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#with_streaming_response
+ """
+ return SnapshotResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ box_id: str,
+ *,
+ name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SnapshotCreateResponse:
+ """Create a snapshot of a running box.
+
+ This snapshot will be saved and can be used
+ to restore the box to the state it was in at the time the snapshot was created.
+
+ Args:
+ name: Name of the snapshot. This name must be unique within the organization.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not box_id:
+ raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
+ return self._post(
+ f"/snapshots/{box_id}",
+ body=maybe_transform({"name": name}, snapshot_create_params.SnapshotCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SnapshotCreateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ # 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,
+ ) -> SnapshotListResponse:
+ """List all snapshots for a given box."""
+ return self._get(
+ "/snapshots",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SnapshotListResponse,
+ )
+
+
+class AsyncSnapshotResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncSnapshotResourceWithRawResponse:
+ """
+ This property can be used as a prefix for any HTTP method call to return
+ the raw response object instead of the parsed content.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#accessing-raw-response-data-eg-headers
+ """
+ return AsyncSnapshotResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncSnapshotResourceWithStreamingResponse:
+ """
+ An alternative to `.with_raw_response` that doesn't eagerly read the response body.
+
+ For more information, see https://www.github.com/babelcloud/gbox-sdk-py#with_streaming_response
+ """
+ return AsyncSnapshotResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ box_id: str,
+ *,
+ name: str,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SnapshotCreateResponse:
+ """Create a snapshot of a running box.
+
+ This snapshot will be saved and can be used
+ to restore the box to the state it was in at the time the snapshot was created.
+
+ Args:
+ name: Name of the snapshot. This name must be unique within the organization.
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not box_id:
+ raise ValueError(f"Expected a non-empty value for `box_id` but received {box_id!r}")
+ return await self._post(
+ f"/snapshots/{box_id}",
+ body=await async_maybe_transform({"name": name}, snapshot_create_params.SnapshotCreateParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SnapshotCreateResponse,
+ )
+
+ async def list(
+ self,
+ *,
+ # 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,
+ ) -> SnapshotListResponse:
+ """List all snapshots for a given box."""
+ return await self._get(
+ "/snapshots",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SnapshotListResponse,
+ )
+
+
+class SnapshotResourceWithRawResponse:
+ def __init__(self, snapshot: SnapshotResource) -> None:
+ self._snapshot = snapshot
+
+ self.create = to_raw_response_wrapper(
+ snapshot.create,
+ )
+ self.list = to_raw_response_wrapper(
+ snapshot.list,
+ )
+
+
+class AsyncSnapshotResourceWithRawResponse:
+ def __init__(self, snapshot: AsyncSnapshotResource) -> None:
+ self._snapshot = snapshot
+
+ self.create = async_to_raw_response_wrapper(
+ snapshot.create,
+ )
+ self.list = async_to_raw_response_wrapper(
+ snapshot.list,
+ )
+
+
+class SnapshotResourceWithStreamingResponse:
+ def __init__(self, snapshot: SnapshotResource) -> None:
+ self._snapshot = snapshot
+
+ self.create = to_streamed_response_wrapper(
+ snapshot.create,
+ )
+ self.list = to_streamed_response_wrapper(
+ snapshot.list,
+ )
+
+
+class AsyncSnapshotResourceWithStreamingResponse:
+ def __init__(self, snapshot: AsyncSnapshotResource) -> None:
+ self._snapshot = snapshot
+
+ self.create = async_to_streamed_response_wrapper(
+ snapshot.create,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ snapshot.list,
+ )
diff --git a/src/gbox_sdk/types/v1/boxes/__init__.py b/src/gbox_sdk/types/v1/boxes/__init__.py
index 3a04fb06..ab040ab1 100644
--- a/src/gbox_sdk/types/v1/boxes/__init__.py
+++ b/src/gbox_sdk/types/v1/boxes/__init__.py
@@ -50,6 +50,8 @@
from .android_restore_params import AndroidRestoreParams as AndroidRestoreParams
from .browser_cdp_url_params import BrowserCdpURLParams as BrowserCdpURLParams
from .detected_element_param import DetectedElementParam as DetectedElementParam
+from .snapshot_create_params import SnapshotCreateParams as SnapshotCreateParams
+from .snapshot_list_response import SnapshotListResponse as SnapshotListResponse
from .action_extract_response import ActionExtractResponse as ActionExtractResponse
from .action_press_key_params import ActionPressKeyParams as ActionPressKeyParams
from .android_list_pkg_params import AndroidListPkgParams as AndroidListPkgParams
@@ -62,6 +64,7 @@
from .browser_cdp_url_response import BrowserCdpURLResponse as BrowserCdpURLResponse
from .browser_set_proxy_params import BrowserSetProxyParams as BrowserSetProxyParams
from .media_get_media_response import MediaGetMediaResponse as MediaGetMediaResponse
+from .snapshot_create_response import SnapshotCreateResponse as SnapshotCreateResponse
from .android_appium_url_params import AndroidAppiumURLParams as AndroidAppiumURLParams
from .android_list_app_response import AndroidListAppResponse as AndroidListAppResponse
from .android_list_pkg_response import AndroidListPkgResponse as AndroidListPkgResponse
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_create_params.py b/src/gbox_sdk/types/v1/boxes/snapshot_create_params.py
new file mode 100644
index 00000000..2b0480d8
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_create_params.py
@@ -0,0 +1,12 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["SnapshotCreateParams"]
+
+
+class SnapshotCreateParams(TypedDict, total=False):
+ name: Required[str]
+ """Name of the snapshot. This name must be unique within the organization."""
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py b/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py
new file mode 100644
index 00000000..b651bd73
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py
@@ -0,0 +1,23 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["SnapshotCreateResponse"]
+
+
+class SnapshotCreateResponse(BaseModel):
+ id: str
+ """Unique identifier for the snapshot"""
+
+ box_type: Literal["linux", "android"] = FieldInfo(alias="boxType")
+ """The type of the box that the snapshot is taken from"""
+
+ name: str
+ """Name of the snapshot. This name must be unique within the organization."""
+
+ provider_type: Literal["vm"] = FieldInfo(alias="providerType")
+ """The provider type of the snapshot"""
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py b/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
new file mode 100644
index 00000000..949dfb48
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
@@ -0,0 +1,38 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["SnapshotListResponse", "Data"]
+
+
+class Data(BaseModel):
+ id: str
+ """Unique identifier for the snapshot"""
+
+ box_type: Literal["linux", "android"] = FieldInfo(alias="boxType")
+ """The type of the box that the snapshot is taken from"""
+
+ name: str
+ """Name of the snapshot. This name must be unique within the organization."""
+
+ provider_type: Literal["vm"] = FieldInfo(alias="providerType")
+ """The provider type of the snapshot"""
+
+
+class SnapshotListResponse(BaseModel):
+ data: List[Data]
+ """List of snapshots"""
+
+ page: int
+ """Page number"""
+
+ page_size: int = FieldInfo(alias="pageSize")
+ """Page size"""
+
+ total: int
+ """Total number of items"""
diff --git a/tests/api_resources/v1/boxes/test_snapshot.py b/tests/api_resources/v1/boxes/test_snapshot.py
new file mode 100644
index 00000000..d48eead3
--- /dev/null
+++ b/tests/api_resources/v1/boxes/test_snapshot.py
@@ -0,0 +1,172 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from gbox_sdk import GboxClient, AsyncGboxClient
+from tests.utils import assert_matches_type
+from gbox_sdk.types.v1.boxes import SnapshotListResponse, SnapshotCreateResponse
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestSnapshot:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_create(self, client: GboxClient) -> None:
+ snapshot = client.v1.boxes.snapshot.create(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ name="my-snapshot-1",
+ )
+ assert_matches_type(SnapshotCreateResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_create(self, client: GboxClient) -> None:
+ response = client.v1.boxes.snapshot.with_raw_response.create(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ name="my-snapshot-1",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = response.parse()
+ assert_matches_type(SnapshotCreateResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_create(self, client: GboxClient) -> None:
+ with client.v1.boxes.snapshot.with_streaming_response.create(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ name="my-snapshot-1",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = response.parse()
+ assert_matches_type(SnapshotCreateResponse, snapshot, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_create(self, client: GboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `box_id` but received ''"):
+ client.v1.boxes.snapshot.with_raw_response.create(
+ box_id="",
+ name="my-snapshot-1",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list(self, client: GboxClient) -> None:
+ snapshot = client.v1.boxes.snapshot.list()
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_list(self, client: GboxClient) -> None:
+ response = client.v1.boxes.snapshot.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = response.parse()
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_list(self, client: GboxClient) -> None:
+ with client.v1.boxes.snapshot.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = response.parse()
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncSnapshot:
+ parametrize = pytest.mark.parametrize(
+ "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"]
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_create(self, async_client: AsyncGboxClient) -> None:
+ snapshot = await async_client.v1.boxes.snapshot.create(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ name="my-snapshot-1",
+ )
+ assert_matches_type(SnapshotCreateResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncGboxClient) -> None:
+ response = await async_client.v1.boxes.snapshot.with_raw_response.create(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ name="my-snapshot-1",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = await response.parse()
+ assert_matches_type(SnapshotCreateResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncGboxClient) -> None:
+ async with async_client.v1.boxes.snapshot.with_streaming_response.create(
+ box_id="c9bdc193-b54b-4ddb-a035-5ac0c598d32d",
+ name="my-snapshot-1",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = await response.parse()
+ assert_matches_type(SnapshotCreateResponse, snapshot, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_create(self, async_client: AsyncGboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `box_id` but received ''"):
+ await async_client.v1.boxes.snapshot.with_raw_response.create(
+ box_id="",
+ name="my-snapshot-1",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list(self, async_client: AsyncGboxClient) -> None:
+ snapshot = await async_client.v1.boxes.snapshot.list()
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncGboxClient) -> None:
+ response = await async_client.v1.boxes.snapshot.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = await response.parse()
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncGboxClient) -> None:
+ async with async_client.v1.boxes.snapshot.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = await response.parse()
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
From 5c627d73b1b930abf69f08c40a2504f62750f6d6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Nov 2025 03:22:37 +0000
Subject: [PATCH 21/31] feat(api): api update
---
.stats.yml | 4 +-
src/gbox_sdk/resources/v1/boxes/actions.py | 1014 ++++++++++++++++-
.../types/v1/boxes/action_click_params.py | 368 +++++-
.../types/v1/boxes/action_click_response.py | 123 +-
tests/api_resources/v1/boxes/test_actions.py | 6 +
5 files changed, 1474 insertions(+), 41 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 456c5d11..623d8991 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 95
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-8846b7e7a8888e676190723800257c36b52a7fc2df16b65587bcf61620739df3.yml
-openapi_spec_hash: e7b0e7cf189e0583fa3bd4e935054853
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-fa6b9650f821ed906def910889cc34728831a8a48b4b385dd36e152fcccaf4dc.yml
+openapi_spec_hash: d61cd9d6bca63b70ee4150c747fc08f8
config_hash: c034f0250abf5ce12e31af4c24a03756
diff --git a/src/gbox_sdk/resources/v1/boxes/actions.py b/src/gbox_sdk/resources/v1/boxes/actions.py
index ded60c32..5b2d041d 100644
--- a/src/gbox_sdk/resources/v1/boxes/actions.py
+++ b/src/gbox_sdk/resources/v1/boxes/actions.py
@@ -91,6 +91,121 @@ def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
@@ -103,7 +218,7 @@ def click(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ActionClickResponse:
"""
- Simulates a click action on the box
+ Simulates a click action on the box.
Args:
x: X coordinate of the click
@@ -119,6 +234,9 @@ def click(
the action response. If false, the screenshot object will still be returned but
with empty URIs. Default is false.
+ modifier_keys: Modifier keys to hold while performing the click (e.g., control, shift, alt).
+ Supports the same key values as the pressKey action.
+
options: Action common options
output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
@@ -169,6 +287,121 @@ def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
@@ -181,7 +414,7 @@ def click(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ActionClickResponse:
"""
- Simulates a click action on the box
+ Simulates a click action on the box.
Args:
target: Describe the target to operate using natural language, e.g., 'login button' or
@@ -196,6 +429,9 @@ def click(
the action response. If false, the screenshot object will still be returned but
with empty URIs. Default is false.
+ modifier_keys: Modifier keys to hold while performing the click (e.g., control, shift, alt).
+ Supports the same key values as the pressKey action.
+
options: Action common options
output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
@@ -246,37 +482,155 @@ def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
- options: ActionCommonOptionsParam | Omit = omit,
- output_format: Literal["base64", "storageKey"] | Omit = omit,
- presigned_expires_in: str | Omit = omit,
- screenshot_delay: str | Omit = omit,
- # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
- # The extra values given here take precedence over values defined on the client or passed to this method.
- extra_headers: Headers | None = None,
- extra_query: Query | None = None,
- extra_body: Body | None = None,
- timeout: float | httpx.Timeout | None | NotGiven = not_given,
- ) -> ActionClickResponse:
- """
- Simulates a click action on the box
-
- Args:
- target: Detected UI element
-
- button: Mouse button to click
-
- double: Whether to perform a double click
-
- include_screenshot: ⚠️ DEPRECATED: Use `options.screenshot.phases` instead. This field will be
- ignored when `options.screenshot` is provided. Whether to include screenshots in
- the action response. If false, the screenshot object will still be returned but
- with empty URIs. Default is false.
-
- options: Action common options
-
- output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
- default is base64. This field will be ignored when `options.screenshot` is
- provided.
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
+ options: ActionCommonOptionsParam | Omit = omit,
+ output_format: Literal["base64", "storageKey"] | Omit = omit,
+ presigned_expires_in: str | Omit = omit,
+ screenshot_delay: str | Omit = omit,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> ActionClickResponse:
+ """
+ Simulates a click action on the box.
+
+ Args:
+ target: Detected UI element
+
+ button: Mouse button to click
+
+ double: Whether to perform a double click
+
+ include_screenshot: ⚠️ DEPRECATED: Use `options.screenshot.phases` instead. This field will be
+ ignored when `options.screenshot` is provided. Whether to include screenshots in
+ the action response. If false, the screenshot object will still be returned but
+ with empty URIs. Default is false.
+
+ modifier_keys: Modifier keys to hold while performing the click (e.g., control, shift, alt).
+ Supports the same key values as the pressKey action.
+
+ options: Action common options
+
+ output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
+ default is base64. This field will be ignored when `options.screenshot` is
+ provided.
presigned_expires_in: ⚠️ DEPRECATED: Use `options.screenshot.presignedExpiresIn` instead. Presigned
url expires in. Only takes effect when outputFormat is storageKey. This field
@@ -323,6 +677,121 @@ def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
@@ -346,6 +815,7 @@ def click(
"button": button,
"double": double,
"include_screenshot": include_screenshot,
+ "modifier_keys": modifier_keys,
"options": options,
"output_format": output_format,
"presigned_expires_in": presigned_expires_in,
@@ -2809,7 +3279,122 @@ async def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
- options: ActionCommonOptionsParam | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
+ options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
screenshot_delay: str | Omit = omit,
@@ -2821,7 +3406,7 @@ async def click(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ActionClickResponse:
"""
- Simulates a click action on the box
+ Simulates a click action on the box.
Args:
x: X coordinate of the click
@@ -2837,6 +3422,9 @@ async def click(
the action response. If false, the screenshot object will still be returned but
with empty URIs. Default is false.
+ modifier_keys: Modifier keys to hold while performing the click (e.g., control, shift, alt).
+ Supports the same key values as the pressKey action.
+
options: Action common options
output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
@@ -2887,6 +3475,121 @@ async def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
@@ -2899,7 +3602,7 @@ async def click(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ActionClickResponse:
"""
- Simulates a click action on the box
+ Simulates a click action on the box.
Args:
target: Describe the target to operate using natural language, e.g., 'login button' or
@@ -2914,6 +3617,9 @@ async def click(
the action response. If false, the screenshot object will still be returned but
with empty URIs. Default is false.
+ modifier_keys: Modifier keys to hold while performing the click (e.g., control, shift, alt).
+ Supports the same key values as the pressKey action.
+
options: Action common options
output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
@@ -2964,6 +3670,121 @@ async def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
@@ -2976,7 +3797,7 @@ async def click(
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> ActionClickResponse:
"""
- Simulates a click action on the box
+ Simulates a click action on the box.
Args:
target: Detected UI element
@@ -2990,6 +3811,9 @@ async def click(
the action response. If false, the screenshot object will still be returned but
with empty URIs. Default is false.
+ modifier_keys: Modifier keys to hold while performing the click (e.g., control, shift, alt).
+ Supports the same key values as the pressKey action.
+
options: Action common options
output_format: ⚠️ DEPRECATED: Use `options.screenshot.outputFormat` instead. Type of the URI.
@@ -3041,6 +3865,121 @@ async def click(
button: Literal["left", "right", "middle"] | Omit = omit,
double: bool | Omit = omit,
include_screenshot: bool | Omit = omit,
+ modifier_keys: List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ | Omit = omit,
options: ActionCommonOptionsParam | Omit = omit,
output_format: Literal["base64", "storageKey"] | Omit = omit,
presigned_expires_in: str | Omit = omit,
@@ -3064,6 +4003,7 @@ async def click(
"button": button,
"double": double,
"include_screenshot": include_screenshot,
+ "modifier_keys": modifier_keys,
"options": options,
"output_format": output_format,
"presigned_expires_in": presigned_expires_in,
diff --git a/src/gbox_sdk/types/v1/boxes/action_click_params.py b/src/gbox_sdk/types/v1/boxes/action_click_params.py
index b2c3cfa3..bd5efc31 100644
--- a/src/gbox_sdk/types/v1/boxes/action_click_params.py
+++ b/src/gbox_sdk/types/v1/boxes/action_click_params.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import Union
+from typing import List, Union
from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict
from ...._utils import PropertyInfo
@@ -33,6 +33,128 @@ class Click(TypedDict, total=False):
still be returned but with empty URIs. Default is false.
"""
+ modifier_keys: Annotated[
+ List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ],
+ PropertyInfo(alias="modifierKeys"),
+ ]
+ """Modifier keys to hold while performing the click (e.g., control, shift, alt).
+
+ Supports the same key values as the pressKey action.
+ """
+
options: ActionCommonOptionsParam
"""Action common options"""
@@ -96,6 +218,128 @@ class ClickByNaturalLanguage(TypedDict, total=False):
still be returned but with empty URIs. Default is false.
"""
+ modifier_keys: Annotated[
+ List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ],
+ PropertyInfo(alias="modifierKeys"),
+ ]
+ """Modifier keys to hold while performing the click (e.g., control, shift, alt).
+
+ Supports the same key values as the pressKey action.
+ """
+
options: ActionCommonOptionsParam
"""Action common options"""
@@ -156,6 +400,128 @@ class ClickByElement(TypedDict, total=False):
still be returned but with empty URIs. Default is false.
"""
+ modifier_keys: Annotated[
+ List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ],
+ PropertyInfo(alias="modifierKeys"),
+ ]
+ """Modifier keys to hold while performing the click (e.g., control, shift, alt).
+
+ Supports the same key values as the pressKey action.
+ """
+
options: ActionCommonOptionsParam
"""Action common options"""
diff --git a/src/gbox_sdk/types/v1/boxes/action_click_response.py b/src/gbox_sdk/types/v1/boxes/action_click_response.py
index dbe5d7e3..aa9e8278 100644
--- a/src/gbox_sdk/types/v1/boxes/action_click_response.py
+++ b/src/gbox_sdk/types/v1/boxes/action_click_response.py
@@ -1,6 +1,6 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import Optional
+from typing import List, Optional
from typing_extensions import Literal
from pydantic import Field as FieldInfo
@@ -23,6 +23,127 @@ class Actual(BaseModel):
y: float
"""Y coordinate where the click was executed"""
+ modifier_keys: Optional[
+ List[
+ Literal[
+ "a",
+ "b",
+ "c",
+ "d",
+ "e",
+ "f",
+ "g",
+ "h",
+ "i",
+ "j",
+ "k",
+ "l",
+ "m",
+ "n",
+ "o",
+ "p",
+ "q",
+ "r",
+ "s",
+ "t",
+ "u",
+ "v",
+ "w",
+ "x",
+ "y",
+ "z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "f1",
+ "f2",
+ "f3",
+ "f4",
+ "f5",
+ "f6",
+ "f7",
+ "f8",
+ "f9",
+ "f10",
+ "f11",
+ "f12",
+ "control",
+ "alt",
+ "shift",
+ "meta",
+ "win",
+ "cmd",
+ "option",
+ "arrowUp",
+ "arrowDown",
+ "arrowLeft",
+ "arrowRight",
+ "home",
+ "end",
+ "pageUp",
+ "pageDown",
+ "enter",
+ "space",
+ "tab",
+ "escape",
+ "backspace",
+ "delete",
+ "insert",
+ "capsLock",
+ "numLock",
+ "scrollLock",
+ "pause",
+ "printScreen",
+ ";",
+ "=",
+ ",",
+ "-",
+ ".",
+ "/",
+ "`",
+ "[",
+ "\\",
+ "]",
+ "'",
+ "numpad0",
+ "numpad1",
+ "numpad2",
+ "numpad3",
+ "numpad4",
+ "numpad5",
+ "numpad6",
+ "numpad7",
+ "numpad8",
+ "numpad9",
+ "numpadAdd",
+ "numpadSubtract",
+ "numpadMultiply",
+ "numpadDivide",
+ "numpadDecimal",
+ "numpadEnter",
+ "numpadEqual",
+ "volumeUp",
+ "volumeDown",
+ "volumeMute",
+ "mediaPlayPause",
+ "mediaStop",
+ "mediaNextTrack",
+ "mediaPreviousTrack",
+ ]
+ ]
+ ] = FieldInfo(alias="modifierKeys", default=None)
+ """Modifier keys that were pressed during the click (e.g., control, shift, alt).
+
+ Matches the KeyboardKey enum used by pressKey action.
+ """
+
class ScreenshotAfter(BaseModel):
uri: str
diff --git a/tests/api_resources/v1/boxes/test_actions.py b/tests/api_resources/v1/boxes/test_actions.py
index 78067854..85e7a32c 100644
--- a/tests/api_resources/v1/boxes/test_actions.py
+++ b/tests/api_resources/v1/boxes/test_actions.py
@@ -56,6 +56,7 @@ def test_method_click_with_all_params_overload_1(self, client: GboxClient) -> No
button="left",
double=False,
include_screenshot=False,
+ modifier_keys=["control", "shift"],
options={
"model": "gpt-5",
"screenshot": {
@@ -129,6 +130,7 @@ def test_method_click_with_all_params_overload_2(self, client: GboxClient) -> No
button="left",
double=False,
include_screenshot=False,
+ modifier_keys=["control", "shift"],
options={
"model": "gpt-5",
"screenshot": {
@@ -229,6 +231,7 @@ def test_method_click_with_all_params_overload_3(self, client: GboxClient) -> No
button="left",
double=False,
include_screenshot=False,
+ modifier_keys=["control", "shift"],
options={
"model": "gpt-5",
"screenshot": {
@@ -2599,6 +2602,7 @@ async def test_method_click_with_all_params_overload_1(self, async_client: Async
button="left",
double=False,
include_screenshot=False,
+ modifier_keys=["control", "shift"],
options={
"model": "gpt-5",
"screenshot": {
@@ -2672,6 +2676,7 @@ async def test_method_click_with_all_params_overload_2(self, async_client: Async
button="left",
double=False,
include_screenshot=False,
+ modifier_keys=["control", "shift"],
options={
"model": "gpt-5",
"screenshot": {
@@ -2772,6 +2777,7 @@ async def test_method_click_with_all_params_overload_3(self, async_client: Async
button="left",
double=False,
include_screenshot=False,
+ modifier_keys=["control", "shift"],
options={
"model": "gpt-5",
"screenshot": {
From e1a762162c96140cb3482c905199cb4cbfba7eb7 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Nov 2025 03:25:37 +0000
Subject: [PATCH 22/31] chore(internal): codegen related update
---
README.md | 4 ++--
pyproject.toml | 5 ++---
src/gbox_sdk/_utils/_sync.py | 34 +++-------------------------------
3 files changed, 7 insertions(+), 36 deletions(-)
diff --git a/README.md b/README.md
index fe56ab5c..c9bbc164 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[)](https://pypi.org/project/gbox_sdk/)
-The Gbox Client Python library provides convenient access to the Gbox Client REST API from any Python 3.8+
+The Gbox Client Python library provides convenient access to the Gbox Client REST API from any Python 3.9+
application. The library includes type definitions for all request params and response fields,
and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx).
@@ -404,7 +404,7 @@ print(gbox_sdk.__version__)
## Requirements
-Python 3.8 or higher.
+Python 3.9 or higher.
## Contributing
diff --git a/pyproject.toml b/pyproject.toml
index d220319e..f45ef9db 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -15,11 +15,10 @@ dependencies = [
"websocket-client>=1.0.0, <2",
"tomli>=2.0.0, <3; python_version < '3.11'",
]
-requires-python = ">= 3.8"
+requires-python = ">= 3.9"
classifiers = [
"Typing :: Typed",
"Intended Audience :: Developers",
- "Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
@@ -131,7 +130,7 @@ filterwarnings = ["error"]
# there are a couple of flags that are still disabled by
# default in strict mode as they are experimental and niche.
typeCheckingMode = "strict"
-pythonVersion = "3.8"
+pythonVersion = "3.9"
# Only include our source code and tests for type checking
include = ["src", "tests"]
diff --git a/src/gbox_sdk/_utils/_sync.py b/src/gbox_sdk/_utils/_sync.py
index ad7ec71b..f6027c18 100644
--- a/src/gbox_sdk/_utils/_sync.py
+++ b/src/gbox_sdk/_utils/_sync.py
@@ -1,10 +1,8 @@
from __future__ import annotations
-import sys
import asyncio
import functools
-import contextvars
-from typing import Any, TypeVar, Callable, Awaitable
+from typing import TypeVar, Callable, Awaitable
from typing_extensions import ParamSpec
import anyio
@@ -15,34 +13,11 @@
T_ParamSpec = ParamSpec("T_ParamSpec")
-if sys.version_info >= (3, 9):
- _asyncio_to_thread = asyncio.to_thread
-else:
- # backport of https://docs.python.org/3/library/asyncio-task.html#asyncio.to_thread
- # for Python 3.8 support
- async def _asyncio_to_thread(
- func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
- ) -> Any:
- """Asynchronously run function *func* in a separate thread.
-
- Any *args and **kwargs supplied for this function are directly passed
- to *func*. Also, the current :class:`contextvars.Context` is propagated,
- allowing context variables from the main thread to be accessed in the
- separate thread.
-
- Returns a coroutine that can be awaited to get the eventual result of *func*.
- """
- loop = asyncio.events.get_running_loop()
- ctx = contextvars.copy_context()
- func_call = functools.partial(ctx.run, func, *args, **kwargs)
- return await loop.run_in_executor(None, func_call)
-
-
async def to_thread(
func: Callable[T_ParamSpec, T_Retval], /, *args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs
) -> T_Retval:
if sniffio.current_async_library() == "asyncio":
- return await _asyncio_to_thread(func, *args, **kwargs)
+ return await asyncio.to_thread(func, *args, **kwargs)
return await anyio.to_thread.run_sync(
functools.partial(func, *args, **kwargs),
@@ -53,10 +28,7 @@ async def to_thread(
def asyncify(function: Callable[T_ParamSpec, T_Retval]) -> Callable[T_ParamSpec, Awaitable[T_Retval]]:
"""
Take a blocking function and create an async one that receives the same
- positional and keyword arguments. For python version 3.9 and above, it uses
- asyncio.to_thread to run the function in a separate thread. For python version
- 3.8, it uses locally defined copy of the asyncio.to_thread function which was
- introduced in python 3.9.
+ positional and keyword arguments.
Usage:
From ffde3585fbe7ad3ef7d849f6e85d40feccadaab3 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 11 Nov 2025 03:26:15 +0000
Subject: [PATCH 23/31] fix: compat with Python 3.14
---
src/gbox_sdk/_models.py | 11 ++++++++---
tests/test_models.py | 8 ++++----
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/src/gbox_sdk/_models.py b/src/gbox_sdk/_models.py
index 6a3cd1d2..fcec2cf9 100644
--- a/src/gbox_sdk/_models.py
+++ b/src/gbox_sdk/_models.py
@@ -2,6 +2,7 @@
import os
import inspect
+import weakref
from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast
from datetime import date, datetime
from typing_extensions import (
@@ -573,6 +574,9 @@ class CachedDiscriminatorType(Protocol):
__discriminator__: DiscriminatorDetails
+DISCRIMINATOR_CACHE: weakref.WeakKeyDictionary[type, DiscriminatorDetails] = weakref.WeakKeyDictionary()
+
+
class DiscriminatorDetails:
field_name: str
"""The name of the discriminator field in the variant class, e.g.
@@ -615,8 +619,9 @@ def __init__(
def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any, ...]) -> DiscriminatorDetails | None:
- if isinstance(union, CachedDiscriminatorType):
- return union.__discriminator__
+ cached = DISCRIMINATOR_CACHE.get(union)
+ if cached is not None:
+ return cached
discriminator_field_name: str | None = None
@@ -669,7 +674,7 @@ def _build_discriminated_union_meta(*, union: type, meta_annotations: tuple[Any,
discriminator_field=discriminator_field_name,
discriminator_alias=discriminator_alias,
)
- cast(CachedDiscriminatorType, union).__discriminator__ = details
+ DISCRIMINATOR_CACHE.setdefault(union, details)
return details
diff --git a/tests/test_models.py b/tests/test_models.py
index 15d305fc..c7d872f5 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -9,7 +9,7 @@
from gbox_sdk._utils import PropertyInfo
from gbox_sdk._compat import PYDANTIC_V1, parse_obj, model_dump, model_json
-from gbox_sdk._models import BaseModel, construct_type
+from gbox_sdk._models import DISCRIMINATOR_CACHE, BaseModel, construct_type
class BasicModel(BaseModel):
@@ -809,7 +809,7 @@ class B(BaseModel):
UnionType = cast(Any, Union[A, B])
- assert not hasattr(UnionType, "__discriminator__")
+ assert not DISCRIMINATOR_CACHE.get(UnionType)
m = construct_type(
value={"type": "b", "data": "foo"}, type_=cast(Any, Annotated[UnionType, PropertyInfo(discriminator="type")])
@@ -818,7 +818,7 @@ class B(BaseModel):
assert m.type == "b"
assert m.data == "foo" # type: ignore[comparison-overlap]
- discriminator = UnionType.__discriminator__
+ discriminator = DISCRIMINATOR_CACHE.get(UnionType)
assert discriminator is not None
m = construct_type(
@@ -830,7 +830,7 @@ class B(BaseModel):
# if the discriminator details object stays the same between invocations then
# we hit the cache
- assert UnionType.__discriminator__ is discriminator
+ assert DISCRIMINATOR_CACHE.get(UnionType) is discriminator
@pytest.mark.skipif(PYDANTIC_V1, reason="TypeAliasType is not supported in Pydantic v1")
From 1c154234a2eff64229c3d1cb2c2d13c76882a9e2 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 12 Nov 2025 03:29:25 +0000
Subject: [PATCH 24/31] fix(compat): update signatures of `model_dump` and
`model_dump_json` for Pydantic v1
---
src/gbox_sdk/_models.py | 41 +++++++++++++++++++++++++++++------------
1 file changed, 29 insertions(+), 12 deletions(-)
diff --git a/src/gbox_sdk/_models.py b/src/gbox_sdk/_models.py
index fcec2cf9..ca9500b2 100644
--- a/src/gbox_sdk/_models.py
+++ b/src/gbox_sdk/_models.py
@@ -257,15 +257,16 @@ def model_dump(
mode: Literal["json", "python"] | str = "python",
include: IncEx | None = None,
exclude: IncEx | None = None,
+ context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
+ exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
- context: dict[str, Any] | None = None,
- serialize_as_any: bool = False,
fallback: Callable[[Any], Any] | None = None,
+ serialize_as_any: bool = False,
) -> dict[str, Any]:
"""Usage docs: https://docs.pydantic.dev/2.4/concepts/serialization/#modelmodel_dump
@@ -273,16 +274,24 @@ def model_dump(
Args:
mode: The mode in which `to_python` should run.
- If mode is 'json', the dictionary will only contain JSON serializable types.
- If mode is 'python', the dictionary may contain any Python objects.
- include: A list of fields to include in the output.
- exclude: A list of fields to exclude from the output.
+ If mode is 'json', the output will only contain JSON serializable types.
+ If mode is 'python', the output may contain non-JSON-serializable Python objects.
+ include: A set of fields to include in the output.
+ exclude: A set of fields to exclude from the output.
+ context: Additional context to pass to the serializer.
by_alias: Whether to use the field's alias in the dictionary key if defined.
- exclude_unset: Whether to exclude fields that are unset or None from the output.
- exclude_defaults: Whether to exclude fields that are set to their default value from the output.
- exclude_none: Whether to exclude fields that have a value of `None` from the output.
- round_trip: Whether to enable serialization and deserialization round-trip support.
- warnings: Whether to log warnings when invalid fields are encountered.
+ exclude_unset: Whether to exclude fields that have not been explicitly set.
+ exclude_defaults: Whether to exclude fields that are set to their default value.
+ exclude_none: Whether to exclude fields that have a value of `None`.
+ exclude_computed_fields: Whether to exclude computed fields.
+ While this can be useful for round-tripping, it is usually recommended to use the dedicated
+ `round_trip` parameter instead.
+ round_trip: If True, dumped values should be valid as input for non-idempotent types such as Json[T].
+ warnings: How to handle serialization errors. False/"none" ignores them, True/"warn" logs errors,
+ "error" raises a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError].
+ fallback: A function to call when an unknown value is encountered. If not provided,
+ a [`PydanticSerializationError`][pydantic_core.PydanticSerializationError] error is raised.
+ serialize_as_any: Whether to serialize fields with duck-typing serialization behavior.
Returns:
A dictionary representation of the model.
@@ -299,6 +308,8 @@ def model_dump(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
+ if exclude_computed_fields != False:
+ raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
dumped = super().dict( # pyright: ignore[reportDeprecated]
include=include,
exclude=exclude,
@@ -315,15 +326,17 @@ def model_dump_json(
self,
*,
indent: int | None = None,
+ ensure_ascii: bool = False,
include: IncEx | None = None,
exclude: IncEx | None = None,
+ context: Any | None = None,
by_alias: bool | None = None,
exclude_unset: bool = False,
exclude_defaults: bool = False,
exclude_none: bool = False,
+ exclude_computed_fields: bool = False,
round_trip: bool = False,
warnings: bool | Literal["none", "warn", "error"] = True,
- context: dict[str, Any] | None = None,
fallback: Callable[[Any], Any] | None = None,
serialize_as_any: bool = False,
) -> str:
@@ -355,6 +368,10 @@ def model_dump_json(
raise ValueError("serialize_as_any is only supported in Pydantic v2")
if fallback is not None:
raise ValueError("fallback is only supported in Pydantic v2")
+ if ensure_ascii != False:
+ raise ValueError("ensure_ascii is only supported in Pydantic v2")
+ if exclude_computed_fields != False:
+ raise ValueError("exclude_computed_fields is only supported in Pydantic v2")
return super().json( # type: ignore[reportDeprecated]
indent=indent,
include=include,
From f7ccac22108864381fbabe34ffe59f3f9d46b879 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 12 Nov 2025 04:09:12 +0000
Subject: [PATCH 25/31] feat(api): api update
---
.stats.yml | 4 ++--
src/gbox_sdk/types/v1/box_create_linux_params.py | 5 ++++-
src/gbox_sdk/types/v1/linux_box.py | 3 +++
tests/api_resources/v1/test_boxes.py | 2 ++
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 623d8991..1cd9a091 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 95
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-fa6b9650f821ed906def910889cc34728831a8a48b4b385dd36e152fcccaf4dc.yml
-openapi_spec_hash: d61cd9d6bca63b70ee4150c747fc08f8
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-943709d3754fffdff75f1286ca60fa3ba43e42118fdd9ee3055cb1e33ccb1784.yml
+openapi_spec_hash: a1802f1d82c6a1479ca7bb42cc798927
config_hash: c034f0250abf5ce12e31af4c24a03756
diff --git a/src/gbox_sdk/types/v1/box_create_linux_params.py b/src/gbox_sdk/types/v1/box_create_linux_params.py
index 335a656f..087ea89c 100644
--- a/src/gbox_sdk/types/v1/box_create_linux_params.py
+++ b/src/gbox_sdk/types/v1/box_create_linux_params.py
@@ -3,7 +3,7 @@
from __future__ import annotations
from typing import Dict
-from typing_extensions import Annotated, TypedDict
+from typing_extensions import Literal, Annotated, TypedDict
from ..._utils import PropertyInfo
@@ -30,6 +30,9 @@ class BoxCreateLinuxParams(TypedDict, total=False):
class Config(TypedDict, total=False):
+ device_type: Annotated[Literal["virtual", "physical"], PropertyInfo(alias="deviceType")]
+ """Device type - virtual or physical Linux device"""
+
envs: Dict[str, str]
"""Environment variables for the box.
diff --git a/src/gbox_sdk/types/v1/linux_box.py b/src/gbox_sdk/types/v1/linux_box.py
index 029f50a0..0a9c5ce1 100644
--- a/src/gbox_sdk/types/v1/linux_box.py
+++ b/src/gbox_sdk/types/v1/linux_box.py
@@ -54,6 +54,9 @@ class Config(BaseModel):
specified otherwise.
"""
+ device_type: Optional[Literal["virtual", "physical"]] = FieldInfo(alias="deviceType", default=None)
+ """Device type - virtual or physical Linux device"""
+
class LinuxBox(BaseModel):
id: str
diff --git a/tests/api_resources/v1/test_boxes.py b/tests/api_resources/v1/test_boxes.py
index 4e4a59e6..b02653a6 100644
--- a/tests/api_resources/v1/test_boxes.py
+++ b/tests/api_resources/v1/test_boxes.py
@@ -175,6 +175,7 @@ def test_method_create_linux(self, client: GboxClient) -> None:
def test_method_create_linux_with_all_params(self, client: GboxClient) -> None:
box = client.v1.boxes.create_linux(
config={
+ "device_type": "virtual",
"envs": {
"DEBUG": "true",
"API_URL": "https://api.example.com",
@@ -876,6 +877,7 @@ async def test_method_create_linux(self, async_client: AsyncGboxClient) -> None:
async def test_method_create_linux_with_all_params(self, async_client: AsyncGboxClient) -> None:
box = await async_client.v1.boxes.create_linux(
config={
+ "device_type": "virtual",
"envs": {
"DEBUG": "true",
"API_URL": "https://api.example.com",
From 2982c2858bbd8dedd9e9551f832d828042e3dc2d Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 13 Nov 2025 02:38:07 +0000
Subject: [PATCH 26/31] feat(api): api update
---
.stats.yml | 4 ++--
src/gbox_sdk/types/v1/box_create_linux_params.py | 4 ++--
src/gbox_sdk/types/v1/linux_box.py | 7 +++++--
tests/api_resources/v1/test_boxes.py | 4 ++--
4 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 1cd9a091..4e26d923 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 95
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-943709d3754fffdff75f1286ca60fa3ba43e42118fdd9ee3055cb1e33ccb1784.yml
-openapi_spec_hash: a1802f1d82c6a1479ca7bb42cc798927
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-c95b1a18769f380da04aa1e921d616c59e6baf3870b7b461c19a8d13e5f03b55.yml
+openapi_spec_hash: be6211ac614aaaba19c579da7628da52
config_hash: c034f0250abf5ce12e31af4c24a03756
diff --git a/src/gbox_sdk/types/v1/box_create_linux_params.py b/src/gbox_sdk/types/v1/box_create_linux_params.py
index 087ea89c..21fed908 100644
--- a/src/gbox_sdk/types/v1/box_create_linux_params.py
+++ b/src/gbox_sdk/types/v1/box_create_linux_params.py
@@ -30,8 +30,8 @@ class BoxCreateLinuxParams(TypedDict, total=False):
class Config(TypedDict, total=False):
- device_type: Annotated[Literal["virtual", "physical"], PropertyInfo(alias="deviceType")]
- """Device type - virtual or physical Linux device"""
+ device_type: Annotated[Literal["container", "vm"], PropertyInfo(alias="deviceType")]
+ """Device type - container or vm Linux device"""
envs: Dict[str, str]
"""Environment variables for the box.
diff --git a/src/gbox_sdk/types/v1/linux_box.py b/src/gbox_sdk/types/v1/linux_box.py
index 0a9c5ce1..90de5b18 100644
--- a/src/gbox_sdk/types/v1/linux_box.py
+++ b/src/gbox_sdk/types/v1/linux_box.py
@@ -42,6 +42,9 @@ class Config(BaseModel):
os: ConfigOs
"""Linux operating system configuration"""
+ public_ip: str = FieldInfo(alias="publicIp")
+ """Public IP allocated to the box."""
+
storage: float
"""Storage allocated to the box in GiB."""
@@ -54,8 +57,8 @@ class Config(BaseModel):
specified otherwise.
"""
- device_type: Optional[Literal["virtual", "physical"]] = FieldInfo(alias="deviceType", default=None)
- """Device type - virtual or physical Linux device"""
+ device_type: Optional[Literal["container", "vm"]] = FieldInfo(alias="deviceType", default=None)
+ """Device type - container or vm Linux device"""
class LinuxBox(BaseModel):
diff --git a/tests/api_resources/v1/test_boxes.py b/tests/api_resources/v1/test_boxes.py
index b02653a6..82b8656e 100644
--- a/tests/api_resources/v1/test_boxes.py
+++ b/tests/api_resources/v1/test_boxes.py
@@ -175,7 +175,7 @@ def test_method_create_linux(self, client: GboxClient) -> None:
def test_method_create_linux_with_all_params(self, client: GboxClient) -> None:
box = client.v1.boxes.create_linux(
config={
- "device_type": "virtual",
+ "device_type": "container",
"envs": {
"DEBUG": "true",
"API_URL": "https://api.example.com",
@@ -877,7 +877,7 @@ async def test_method_create_linux(self, async_client: AsyncGboxClient) -> None:
async def test_method_create_linux_with_all_params(self, async_client: AsyncGboxClient) -> None:
box = await async_client.v1.boxes.create_linux(
config={
- "device_type": "virtual",
+ "device_type": "container",
"envs": {
"DEBUG": "true",
"API_URL": "https://api.example.com",
From b13f7726b1afc0b339540dddfd9fe17619167ba6 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 20 Nov 2025 15:57:23 +0000
Subject: [PATCH 27/31] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 4e26d923..e23f1810 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 95
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-c95b1a18769f380da04aa1e921d616c59e6baf3870b7b461c19a8d13e5f03b55.yml
-openapi_spec_hash: be6211ac614aaaba19c579da7628da52
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-5079febcc37ea37f5449d9e6713dd2c5381b0c6025018357976f52457a801dbd.yml
+openapi_spec_hash: 7a7fac932954247f06e3a3d605174144
config_hash: c034f0250abf5ce12e31af4c24a03756
From 587ba188e28daa293d6fc07fadb02787a191e19e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 02:17:12 +0000
Subject: [PATCH 28/31] feat(api): api update
---
.stats.yml | 8 +-
api.md | 10 +-
src/gbox_sdk/resources/v1/boxes/snapshot.py | 225 +++++++++++++++++-
.../types/v1/box_create_linux_params.py | 3 +
src/gbox_sdk/types/v1/boxes/__init__.py | 2 +
.../v1/boxes/snapshot_create_response.py | 3 +
.../types/v1/boxes/snapshot_get_response.py | 26 ++
.../types/v1/boxes/snapshot_list_params.py | 17 ++
.../types/v1/boxes/snapshot_list_response.py | 3 +
src/gbox_sdk/types/v1/linux_box.py | 6 +
tests/api_resources/v1/boxes/test_snapshot.py | 192 ++++++++++++++-
tests/api_resources/v1/test_boxes.py | 2 +
12 files changed, 484 insertions(+), 13 deletions(-)
create mode 100644 src/gbox_sdk/types/v1/boxes/snapshot_get_response.py
create mode 100644 src/gbox_sdk/types/v1/boxes/snapshot_list_params.py
diff --git a/.stats.yml b/.stats.yml
index e23f1810..44993b5c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 95
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-5079febcc37ea37f5449d9e6713dd2c5381b0c6025018357976f52457a801dbd.yml
-openapi_spec_hash: 7a7fac932954247f06e3a3d605174144
-config_hash: c034f0250abf5ce12e31af4c24a03756
+configured_endpoints: 97
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/gbox%2Fgbox-sdk-55261ae727564574a5184bc78c51ec929dcbca1baa07fbcd9441e074d93ba533.yml
+openapi_spec_hash: cc0e4abc5e50e1a4adea8d8043b52d7e
+config_hash: 6812a7e5258816ba39d8ec1311a62f45
diff --git a/api.md b/api.md
index c8c5efde..637a9607 100644
--- a/api.md
+++ b/api.md
@@ -143,13 +143,19 @@ Methods:
Types:
```python
-from gbox_sdk.types.v1.boxes import SnapshotCreateResponse, SnapshotListResponse
+from gbox_sdk.types.v1.boxes import (
+ SnapshotCreateResponse,
+ SnapshotListResponse,
+ SnapshotGetResponse,
+)
```
Methods:
- client.v1.boxes.snapshot.create(box_id, \*\*params) -> SnapshotCreateResponse
-- client.v1.boxes.snapshot.list() -> SnapshotListResponse
+- client.v1.boxes.snapshot.list(\*\*params) -> SnapshotListResponse
+- client.v1.boxes.snapshot.get(snapshot_name) -> SnapshotGetResponse
+- client.v1.boxes.snapshot.remove(snapshot_name) -> None
### Proxy
diff --git a/src/gbox_sdk/resources/v1/boxes/snapshot.py b/src/gbox_sdk/resources/v1/boxes/snapshot.py
index 470bef79..6305aded 100644
--- a/src/gbox_sdk/resources/v1/boxes/snapshot.py
+++ b/src/gbox_sdk/resources/v1/boxes/snapshot.py
@@ -4,7 +4,7 @@
import httpx
-from ...._types import Body, Query, Headers, NotGiven, not_given
+from ...._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given
from ...._utils import maybe_transform, async_maybe_transform
from ...._compat import cached_property
from ...._resource import SyncAPIResource, AsyncAPIResource
@@ -15,7 +15,8 @@
async_to_streamed_response_wrapper,
)
from ...._base_client import make_request_options
-from ....types.v1.boxes import snapshot_create_params
+from ....types.v1.boxes import snapshot_list_params, snapshot_create_params
+from ....types.v1.boxes.snapshot_get_response import SnapshotGetResponse
from ....types.v1.boxes.snapshot_list_response import SnapshotListResponse
from ....types.v1.boxes.snapshot_create_response import SnapshotCreateResponse
@@ -84,6 +85,8 @@ def create(
def list(
self,
*,
+ page: int | Omit = omit,
+ page_size: 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,
@@ -91,15 +94,107 @@ def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SnapshotListResponse:
- """List all snapshots for a given box."""
+ """
+ List all snapshots of current orginazation.
+
+ Args:
+ page: Page number
+
+ page_size: Page size
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
return self._get(
"/snapshots",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "page": page,
+ "page_size": page_size,
+ },
+ snapshot_list_params.SnapshotListParams,
+ ),
),
cast_to=SnapshotListResponse,
)
+ def get(
+ self,
+ snapshot_name: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SnapshotGetResponse:
+ """
+ Get a snapshot with specified name
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not snapshot_name:
+ raise ValueError(f"Expected a non-empty value for `snapshot_name` but received {snapshot_name!r}")
+ return self._get(
+ f"/snapshots/{snapshot_name}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SnapshotGetResponse,
+ )
+
+ def remove(
+ self,
+ snapshot_name: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Remove a snapshot of specified id.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not snapshot_name:
+ raise ValueError(f"Expected a non-empty value for `snapshot_name` but received {snapshot_name!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return self._delete(
+ f"/snapshots/{snapshot_name}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
class AsyncSnapshotResource(AsyncAPIResource):
@cached_property
@@ -163,6 +258,8 @@ async def create(
async def list(
self,
*,
+ page: int | Omit = omit,
+ page_size: 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,
@@ -170,15 +267,107 @@ async def list(
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> SnapshotListResponse:
- """List all snapshots for a given box."""
+ """
+ List all snapshots of current orginazation.
+
+ Args:
+ page: Page number
+
+ page_size: Page size
+
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
return await self._get(
"/snapshots",
options=make_request_options(
- extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=await async_maybe_transform(
+ {
+ "page": page,
+ "page_size": page_size,
+ },
+ snapshot_list_params.SnapshotListParams,
+ ),
),
cast_to=SnapshotListResponse,
)
+ async def get(
+ self,
+ snapshot_name: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SnapshotGetResponse:
+ """
+ Get a snapshot with specified name
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not snapshot_name:
+ raise ValueError(f"Expected a non-empty value for `snapshot_name` but received {snapshot_name!r}")
+ return await self._get(
+ f"/snapshots/{snapshot_name}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=SnapshotGetResponse,
+ )
+
+ async def remove(
+ self,
+ snapshot_name: str,
+ *,
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> None:
+ """
+ Remove a snapshot of specified id.
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not snapshot_name:
+ raise ValueError(f"Expected a non-empty value for `snapshot_name` but received {snapshot_name!r}")
+ extra_headers = {"Accept": "*/*", **(extra_headers or {})}
+ return await self._delete(
+ f"/snapshots/{snapshot_name}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=NoneType,
+ )
+
class SnapshotResourceWithRawResponse:
def __init__(self, snapshot: SnapshotResource) -> None:
@@ -190,6 +379,12 @@ def __init__(self, snapshot: SnapshotResource) -> None:
self.list = to_raw_response_wrapper(
snapshot.list,
)
+ self.get = to_raw_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = to_raw_response_wrapper(
+ snapshot.remove,
+ )
class AsyncSnapshotResourceWithRawResponse:
@@ -202,6 +397,12 @@ def __init__(self, snapshot: AsyncSnapshotResource) -> None:
self.list = async_to_raw_response_wrapper(
snapshot.list,
)
+ self.get = async_to_raw_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ snapshot.remove,
+ )
class SnapshotResourceWithStreamingResponse:
@@ -214,6 +415,12 @@ def __init__(self, snapshot: SnapshotResource) -> None:
self.list = to_streamed_response_wrapper(
snapshot.list,
)
+ self.get = to_streamed_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = to_streamed_response_wrapper(
+ snapshot.remove,
+ )
class AsyncSnapshotResourceWithStreamingResponse:
@@ -226,3 +433,9 @@ def __init__(self, snapshot: AsyncSnapshotResource) -> None:
self.list = async_to_streamed_response_wrapper(
snapshot.list,
)
+ self.get = async_to_streamed_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ snapshot.remove,
+ )
diff --git a/src/gbox_sdk/types/v1/box_create_linux_params.py b/src/gbox_sdk/types/v1/box_create_linux_params.py
index 21fed908..e2a56aa0 100644
--- a/src/gbox_sdk/types/v1/box_create_linux_params.py
+++ b/src/gbox_sdk/types/v1/box_create_linux_params.py
@@ -71,3 +71,6 @@ class Config(TypedDict, total=False):
applications, or any other organizational tags that help you organize and filter
your boxes.
"""
+
+ snapshot_name: Annotated[str, PropertyInfo(alias="snapshotName")]
+ """Snapshot name - snapshot for creating vm linux box"""
diff --git a/src/gbox_sdk/types/v1/boxes/__init__.py b/src/gbox_sdk/types/v1/boxes/__init__.py
index ab040ab1..22282518 100644
--- a/src/gbox_sdk/types/v1/boxes/__init__.py
+++ b/src/gbox_sdk/types/v1/boxes/__init__.py
@@ -39,11 +39,13 @@
from .browser_open_params import BrowserOpenParams as BrowserOpenParams
from .action_drag_response import ActionDragResponse as ActionDragResponse
from .action_scroll_params import ActionScrollParams as ActionScrollParams
+from .snapshot_list_params import SnapshotListParams as SnapshotListParams
from .action_click_response import ActionClickResponse as ActionClickResponse
from .action_extract_params import ActionExtractParams as ActionExtractParams
from .action_swipe_response import ActionSwipeResponse as ActionSwipeResponse
from .action_touch_response import ActionTouchResponse as ActionTouchResponse
from .browser_open_response import BrowserOpenResponse as BrowserOpenResponse
+from .snapshot_get_response import SnapshotGetResponse as SnapshotGetResponse
from .action_scroll_response import ActionScrollResponse as ActionScrollResponse
from .android_install_params import AndroidInstallParams as AndroidInstallParams
from .android_restart_params import AndroidRestartParams as AndroidRestartParams
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py b/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py
index b651bd73..6ef24683 100644
--- a/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_create_response.py
@@ -21,3 +21,6 @@ class SnapshotCreateResponse(BaseModel):
provider_type: Literal["vm"] = FieldInfo(alias="providerType")
"""The provider type of the snapshot"""
+
+ status: Literal["Pending", "Available", "Error"]
+ """The status of the snapshot"""
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_get_response.py b/src/gbox_sdk/types/v1/boxes/snapshot_get_response.py
new file mode 100644
index 00000000..918d58d0
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_get_response.py
@@ -0,0 +1,26 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from pydantic import Field as FieldInfo
+
+from ...._models import BaseModel
+
+__all__ = ["SnapshotGetResponse"]
+
+
+class SnapshotGetResponse(BaseModel):
+ id: str
+ """Unique identifier for the snapshot"""
+
+ box_type: Literal["linux", "android"] = FieldInfo(alias="boxType")
+ """The type of the box that the snapshot is taken from"""
+
+ name: str
+ """Name of the snapshot. This name must be unique within the organization."""
+
+ provider_type: Literal["vm"] = FieldInfo(alias="providerType")
+ """The provider type of the snapshot"""
+
+ status: Literal["Pending", "Available", "Error"]
+ """The status of the snapshot"""
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_list_params.py b/src/gbox_sdk/types/v1/boxes/snapshot_list_params.py
new file mode 100644
index 00000000..c418a234
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_list_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Annotated, TypedDict
+
+from ...._utils import PropertyInfo
+
+__all__ = ["SnapshotListParams"]
+
+
+class SnapshotListParams(TypedDict, total=False):
+ page: int
+ """Page number"""
+
+ page_size: Annotated[int, PropertyInfo(alias="pageSize")]
+ """Page size"""
diff --git a/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py b/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
index 949dfb48..53dcf2a2 100644
--- a/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
@@ -23,6 +23,9 @@ class Data(BaseModel):
provider_type: Literal["vm"] = FieldInfo(alias="providerType")
"""The provider type of the snapshot"""
+ status: Literal["Pending", "Available", "Error"]
+ """The status of the snapshot"""
+
class SnapshotListResponse(BaseModel):
data: List[Data]
diff --git a/src/gbox_sdk/types/v1/linux_box.py b/src/gbox_sdk/types/v1/linux_box.py
index 90de5b18..44c050e7 100644
--- a/src/gbox_sdk/types/v1/linux_box.py
+++ b/src/gbox_sdk/types/v1/linux_box.py
@@ -60,6 +60,12 @@ class Config(BaseModel):
device_type: Optional[Literal["container", "vm"]] = FieldInfo(alias="deviceType", default=None)
"""Device type - container or vm Linux device"""
+ snapshot_id: Optional[str] = FieldInfo(alias="snapshotId", default=None)
+ """Snapshot id"""
+
+ snapshot_name: Optional[str] = FieldInfo(alias="snapshotName", default=None)
+ """Snapshot name"""
+
class LinuxBox(BaseModel):
id: str
diff --git a/tests/api_resources/v1/boxes/test_snapshot.py b/tests/api_resources/v1/boxes/test_snapshot.py
index d48eead3..343fa976 100644
--- a/tests/api_resources/v1/boxes/test_snapshot.py
+++ b/tests/api_resources/v1/boxes/test_snapshot.py
@@ -9,7 +9,11 @@
from gbox_sdk import GboxClient, AsyncGboxClient
from tests.utils import assert_matches_type
-from gbox_sdk.types.v1.boxes import SnapshotListResponse, SnapshotCreateResponse
+from gbox_sdk.types.v1.boxes import (
+ SnapshotGetResponse,
+ SnapshotListResponse,
+ SnapshotCreateResponse,
+)
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -69,6 +73,15 @@ def test_method_list(self, client: GboxClient) -> None:
snapshot = client.v1.boxes.snapshot.list()
assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_list_with_all_params(self, client: GboxClient) -> None:
+ snapshot = client.v1.boxes.snapshot.list(
+ page=1,
+ page_size=10,
+ )
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_raw_response_list(self, client: GboxClient) -> None:
@@ -91,6 +104,90 @@ def test_streaming_response_list(self, client: GboxClient) -> None:
assert cast(Any, response.is_closed) is True
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_get(self, client: GboxClient) -> None:
+ snapshot = client.v1.boxes.snapshot.get(
+ "snapshotName",
+ )
+ assert_matches_type(SnapshotGetResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_get(self, client: GboxClient) -> None:
+ response = client.v1.boxes.snapshot.with_raw_response.get(
+ "snapshotName",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = response.parse()
+ assert_matches_type(SnapshotGetResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_get(self, client: GboxClient) -> None:
+ with client.v1.boxes.snapshot.with_streaming_response.get(
+ "snapshotName",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = response.parse()
+ assert_matches_type(SnapshotGetResponse, snapshot, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_get(self, client: GboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `snapshot_name` but received ''"):
+ client.v1.boxes.snapshot.with_raw_response.get(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_method_remove(self, client: GboxClient) -> None:
+ snapshot = client.v1.boxes.snapshot.remove(
+ "snapshotName",
+ )
+ assert snapshot is None
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_raw_response_remove(self, client: GboxClient) -> None:
+ response = client.v1.boxes.snapshot.with_raw_response.remove(
+ "snapshotName",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = response.parse()
+ assert snapshot is None
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_streaming_response_remove(self, client: GboxClient) -> None:
+ with client.v1.boxes.snapshot.with_streaming_response.remove(
+ "snapshotName",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = response.parse()
+ assert snapshot is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ def test_path_params_remove(self, client: GboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `snapshot_name` but received ''"):
+ client.v1.boxes.snapshot.with_raw_response.remove(
+ "",
+ )
+
class TestAsyncSnapshot:
parametrize = pytest.mark.parametrize(
@@ -149,6 +246,15 @@ async def test_method_list(self, async_client: AsyncGboxClient) -> None:
snapshot = await async_client.v1.boxes.snapshot.list()
assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncGboxClient) -> None:
+ snapshot = await async_client.v1.boxes.snapshot.list(
+ page=1,
+ page_size=10,
+ )
+ assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
+
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_raw_response_list(self, async_client: AsyncGboxClient) -> None:
@@ -170,3 +276,87 @@ async def test_streaming_response_list(self, async_client: AsyncGboxClient) -> N
assert_matches_type(SnapshotListResponse, snapshot, path=["response"])
assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_get(self, async_client: AsyncGboxClient) -> None:
+ snapshot = await async_client.v1.boxes.snapshot.get(
+ "snapshotName",
+ )
+ assert_matches_type(SnapshotGetResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_get(self, async_client: AsyncGboxClient) -> None:
+ response = await async_client.v1.boxes.snapshot.with_raw_response.get(
+ "snapshotName",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = await response.parse()
+ assert_matches_type(SnapshotGetResponse, snapshot, path=["response"])
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_get(self, async_client: AsyncGboxClient) -> None:
+ async with async_client.v1.boxes.snapshot.with_streaming_response.get(
+ "snapshotName",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = await response.parse()
+ assert_matches_type(SnapshotGetResponse, snapshot, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_get(self, async_client: AsyncGboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `snapshot_name` but received ''"):
+ await async_client.v1.boxes.snapshot.with_raw_response.get(
+ "",
+ )
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_method_remove(self, async_client: AsyncGboxClient) -> None:
+ snapshot = await async_client.v1.boxes.snapshot.remove(
+ "snapshotName",
+ )
+ assert snapshot is None
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_raw_response_remove(self, async_client: AsyncGboxClient) -> None:
+ response = await async_client.v1.boxes.snapshot.with_raw_response.remove(
+ "snapshotName",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ snapshot = await response.parse()
+ assert snapshot is None
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_streaming_response_remove(self, async_client: AsyncGboxClient) -> None:
+ async with async_client.v1.boxes.snapshot.with_streaming_response.remove(
+ "snapshotName",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ snapshot = await response.parse()
+ assert snapshot is None
+
+ assert cast(Any, response.is_closed) is True
+
+ @pytest.mark.skip(reason="Prism tests are disabled")
+ @parametrize
+ async def test_path_params_remove(self, async_client: AsyncGboxClient) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `snapshot_name` but received ''"):
+ await async_client.v1.boxes.snapshot.with_raw_response.remove(
+ "",
+ )
diff --git a/tests/api_resources/v1/test_boxes.py b/tests/api_resources/v1/test_boxes.py
index 82b8656e..4caf20d8 100644
--- a/tests/api_resources/v1/test_boxes.py
+++ b/tests/api_resources/v1/test_boxes.py
@@ -186,6 +186,7 @@ def test_method_create_linux_with_all_params(self, client: GboxClient) -> None:
"project": "web-automation",
"environment": "testing",
},
+ "snapshot_name": "snapshotName",
},
api_timeout="30s",
wait=True,
@@ -888,6 +889,7 @@ async def test_method_create_linux_with_all_params(self, async_client: AsyncGbox
"project": "web-automation",
"environment": "testing",
},
+ "snapshot_name": "snapshotName",
},
api_timeout="30s",
wait=True,
From 0aa85916194fa140e06323c593bcd686ac327056 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Sat, 22 Nov 2025 03:18:54 +0000
Subject: [PATCH 29/31] chore: add Python 3.14 classifier and testing
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index f45ef9db..7c3326b2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -24,6 +24,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Operating System :: OS Independent",
"Operating System :: POSIX",
"Operating System :: MacOS",
From e4ef68a67b7cc3d402b633ad805006b9fa36bc9e Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Nov 2025 03:06:50 +0000
Subject: [PATCH 30/31] fix: ensure streams are always closed
---
src/gbox_sdk/_streaming.py | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/src/gbox_sdk/_streaming.py b/src/gbox_sdk/_streaming.py
index 05f67073..c38860a3 100644
--- a/src/gbox_sdk/_streaming.py
+++ b/src/gbox_sdk/_streaming.py
@@ -54,11 +54,12 @@ def __stream__(self) -> Iterator[_T]:
process_data = self._client._process_response_data
iterator = self._iter_events()
- for sse in iterator:
- yield process_data(data=sse.json(), cast_to=cast_to, response=response)
-
- # As we might not fully consume the response stream, we need to close it explicitly
- response.close()
+ try:
+ for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ response.close()
def __enter__(self) -> Self:
return self
@@ -117,11 +118,12 @@ async def __stream__(self) -> AsyncIterator[_T]:
process_data = self._client._process_response_data
iterator = self._iter_events()
- async for sse in iterator:
- yield process_data(data=sse.json(), cast_to=cast_to, response=response)
-
- # As we might not fully consume the response stream, we need to close it explicitly
- await response.aclose()
+ try:
+ async for sse in iterator:
+ yield process_data(data=sse.json(), cast_to=cast_to, response=response)
+ finally:
+ # Ensure the response is closed even if the consumer doesn't read all data
+ await response.aclose()
async def __aenter__(self) -> Self:
return self
From bd510e17aef6cbfbcc9732a8be796d6ce4a35d5c Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Nov 2025 03:07:17 +0000
Subject: [PATCH 31/31] release: 0.1.0-alpha.42
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++
pyproject.toml | 2 +-
src/gbox_sdk/_version.py | 2 +-
4 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 9c28aaea..859ebd32 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.1.0-alpha.41"
+ ".": "0.1.0-alpha.42"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b864350..a5bf1fa9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,44 @@
# Changelog
+## 0.1.0-alpha.42 (2025-11-28)
+
+Full Changelog: [v0.1.0-alpha.41...v0.1.0-alpha.42](https://github.com/babelcloud/gbox-sdk-py/compare/v0.1.0-alpha.41...v0.1.0-alpha.42)
+
+### Features
+
+* **api:** api update ([587ba18](https://github.com/babelcloud/gbox-sdk-py/commit/587ba188e28daa293d6fc07fadb02787a191e19e))
+* **api:** api update ([2982c28](https://github.com/babelcloud/gbox-sdk-py/commit/2982c2858bbd8dedd9e9551f832d828042e3dc2d))
+* **api:** api update ([f7ccac2](https://github.com/babelcloud/gbox-sdk-py/commit/f7ccac22108864381fbabe34ffe59f3f9d46b879))
+* **api:** api update ([5c627d7](https://github.com/babelcloud/gbox-sdk-py/commit/5c627d73b1b930abf69f08c40a2504f62750f6d6))
+* **api:** api update ([df5876e](https://github.com/babelcloud/gbox-sdk-py/commit/df5876ed05231ee5b90ccff7bf2851897d974a8e))
+* **api:** api update ([b416416](https://github.com/babelcloud/gbox-sdk-py/commit/b4164163611f49880e86e852395df0ebf7c4ce0e))
+* **api:** api update ([7d0d335](https://github.com/babelcloud/gbox-sdk-py/commit/7d0d335f15219ee0262bb615821ba975fbec5902))
+* **api:** api update ([81bd4ad](https://github.com/babelcloud/gbox-sdk-py/commit/81bd4ad176524c933d50f44f47a20eba6f883f50))
+* **api:** api update ([68ce162](https://github.com/babelcloud/gbox-sdk-py/commit/68ce1621ebd5200b9da3c5d33d7e66e31a0267c9))
+* **api:** api update ([69414a0](https://github.com/babelcloud/gbox-sdk-py/commit/69414a0450da6b6a2f77aefefa25e925fabbc12c))
+* **api:** api update ([bf80a71](https://github.com/babelcloud/gbox-sdk-py/commit/bf80a71aac618785c3e3775be0ec47c2f740b636))
+* **api:** api update ([21a9d25](https://github.com/babelcloud/gbox-sdk-py/commit/21a9d25006827c3aa81e394dc4683f944d120870))
+* **api:** api update ([a4fa084](https://github.com/babelcloud/gbox-sdk-py/commit/a4fa084a0028968e2c5c6bffb84990d24cb32820))
+* **api:** api update ([874ec44](https://github.com/babelcloud/gbox-sdk-py/commit/874ec440b07600afbc0974fab08bdf477936133c))
+* **api:** api update ([7ab29ec](https://github.com/babelcloud/gbox-sdk-py/commit/7ab29ec5417489f1a2b168a9bc6d3f3801d250e6))
+
+
+### Bug Fixes
+
+* **client:** close streams without requiring full consumption ([12bd9ce](https://github.com/babelcloud/gbox-sdk-py/commit/12bd9ce7b5e266cc7791697c8d707b9a962b1b68))
+* compat with Python 3.14 ([ffde358](https://github.com/babelcloud/gbox-sdk-py/commit/ffde3585fbe7ad3ef7d849f6e85d40feccadaab3))
+* **compat:** update signatures of `model_dump` and `model_dump_json` for Pydantic v1 ([1c15423](https://github.com/babelcloud/gbox-sdk-py/commit/1c154234a2eff64229c3d1cb2c2d13c76882a9e2))
+* ensure streams are always closed ([e4ef68a](https://github.com/babelcloud/gbox-sdk-py/commit/e4ef68a67b7cc3d402b633ad805006b9fa36bc9e))
+
+
+### Chores
+
+* add Python 3.14 classifier and testing ([0aa8591](https://github.com/babelcloud/gbox-sdk-py/commit/0aa85916194fa140e06323c593bcd686ac327056))
+* bump `httpx-aiohttp` version to 0.1.9 ([d3b0ab9](https://github.com/babelcloud/gbox-sdk-py/commit/d3b0ab939b6ea1ba076374da27f8ea13813b3a45))
+* **internal/tests:** avoid race condition with implicit client cleanup ([0178cf3](https://github.com/babelcloud/gbox-sdk-py/commit/0178cf3a021f94850ec707e63f759cd24836128b))
+* **internal:** codegen related update ([e1a7621](https://github.com/babelcloud/gbox-sdk-py/commit/e1a762162c96140cb3482c905199cb4cbfba7eb7))
+* **internal:** grammar fix (it's -> its) ([57b312c](https://github.com/babelcloud/gbox-sdk-py/commit/57b312cf2f7abfb5f2d38838c072ce30d178b375))
+
## 0.1.0-alpha.41 (2025-10-14)
Full Changelog: [v0.1.0-alpha.40...v0.1.0-alpha.41](https://github.com/babelcloud/gbox-sdk-py/compare/v0.1.0-alpha.40...v0.1.0-alpha.41)
diff --git a/pyproject.toml b/pyproject.toml
index 7c3326b2..5b54f309 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "gbox_sdk"
-version = "0.1.0-alpha.41"
+version = "0.1.0-alpha.42"
description = "The official Python library for the gbox-client API"
dynamic = ["readme"]
license = "Apache-2.0"
diff --git a/src/gbox_sdk/_version.py b/src/gbox_sdk/_version.py
index b945851b..0960f11f 100644
--- a/src/gbox_sdk/_version.py
+++ b/src/gbox_sdk/_version.py
@@ -1,4 +1,4 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
__title__ = "gbox_sdk"
-__version__ = "0.1.0-alpha.41" # x-release-please-version
+__version__ = "0.1.0-alpha.42" # x-release-please-version