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/.stats.yml b/.stats.yml
index 9f14195c..44993b5c 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: 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/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/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/api.md b/api.md
index bcb3af52..637a9607 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:
@@ -77,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
@@ -110,15 +129,34 @@ 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
+### Snapshot
+
+Types:
+
+```python
+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(\*\*params) -> SnapshotListResponse
+- client.v1.boxes.snapshot.get(snapshot_name) -> SnapshotGetResponse
+- client.v1.boxes.snapshot.remove(snapshot_name) -> None
+
### Proxy
Types:
@@ -228,6 +266,7 @@ Types:
from gbox_sdk.types.v1.boxes import (
AndroidApp,
AndroidPkg,
+ AndroidAppiumURLResponse,
AndroidGetConnectAddressResponse,
AndroidInstallResponse,
AndroidListActivitiesResponse,
@@ -239,6 +278,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/pyproject.toml b/pyproject.toml
index d30a5df1..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"
@@ -15,16 +15,16 @@ 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",
"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",
@@ -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]
@@ -131,7 +131,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/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
diff --git a/src/gbox_sdk/_models.py b/src/gbox_sdk/_models.py
index 6a3cd1d2..ca9500b2 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 (
@@ -256,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
@@ -272,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.
@@ -298,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,
@@ -314,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:
@@ -354,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,
@@ -573,6 +591,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 +636,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 +691,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/src/gbox_sdk/_streaming.py b/src/gbox_sdk/_streaming.py
index 41efee78..c38860a3 100644
--- a/src/gbox_sdk/_streaming.py
+++ b/src/gbox_sdk/_streaming.py
@@ -54,12 +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)
-
- # Ensure the entire stream is consumed
- for _sse in iterator:
- ...
+ 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
@@ -118,12 +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)
-
- # Ensure the entire stream is consumed
- async for _sse in iterator:
- ...
+ 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
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:
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
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
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/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/actions.py b/src/gbox_sdk/resources/v1/boxes/actions.py
index 4f350e67..5b2d041d 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
@@ -84,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,
@@ -94,9 +216,9 @@ 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
+ Simulates a click action on the box.
Args:
x: X coordinate of the click
@@ -112,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.
@@ -162,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,
@@ -172,9 +412,9 @@ 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
+ Simulates a click action on the box.
Args:
target: Describe the target to operate using natural language, e.g., 'login button' or
@@ -189,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.
@@ -239,6 +482,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,
@@ -249,9 +607,9 @@ 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
+ Simulates a click action on the box.
Args:
target: Detected UI element
@@ -265,6 +623,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.
@@ -316,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,
@@ -327,7 +803,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(
@@ -339,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,
@@ -350,7 +827,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 +920,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 +1000,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 +1080,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 +1102,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 +1219,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 +1299,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 +1378,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 +1458,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 +1480,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 +2268,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 +2352,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 +2447,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 +2473,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 +2608,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 +2693,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 +2775,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 +2799,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 +2820,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 +2892,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 +2963,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 +3035,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 +3056,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 +3075,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 +3146,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(
@@ -2802,6 +3279,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,
@@ -2812,9 +3404,9 @@ 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
+ Simulates a click action on the box.
Args:
x: X coordinate of the click
@@ -2830,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.
@@ -2880,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,
@@ -2890,9 +3600,9 @@ 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
+ Simulates a click action on the box.
Args:
target: Describe the target to operate using natural language, e.g., 'login button' or
@@ -2907,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.
@@ -2957,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,
@@ -2967,9 +3795,9 @@ 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
+ Simulates a click action on the box.
Args:
target: Detected UI element
@@ -2983,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.
@@ -3034,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,
@@ -3045,7 +3991,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(
@@ -3057,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,
@@ -3068,7 +4015,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 +4110,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 +4190,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 +4270,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 +4292,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 +4411,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 +4491,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 +4570,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 +4650,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 +4672,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 +5462,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 +5546,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 +5641,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 +5667,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 +5804,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 +5889,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 +5971,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 +5995,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 +6016,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 +6088,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 +6159,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 +6231,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 +6252,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 +6271,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 +6342,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/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/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..6305aded
--- /dev/null
+++ b/src/gbox_sdk/resources/v1/boxes/snapshot.py
@@ -0,0 +1,441 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import httpx
+
+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
+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_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
+
+__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,
+ *,
+ 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SnapshotListResponse:
+ """
+ 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,
+ 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
+ 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,
+ *,
+ 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,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = not_given,
+ ) -> SnapshotListResponse:
+ """
+ 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,
+ 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:
+ self._snapshot = snapshot
+
+ self.create = to_raw_response_wrapper(
+ snapshot.create,
+ )
+ 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:
+ 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,
+ )
+ self.get = async_to_raw_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = async_to_raw_response_wrapper(
+ snapshot.remove,
+ )
+
+
+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,
+ )
+ self.get = to_streamed_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = to_streamed_response_wrapper(
+ snapshot.remove,
+ )
+
+
+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,
+ )
+ self.get = async_to_streamed_response_wrapper(
+ snapshot.get,
+ )
+ self.remove = async_to_streamed_response_wrapper(
+ snapshot.remove,
+ )
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/resources/v1/models.py b/src/gbox_sdk/resources/v1/models.py
new file mode 100644
index 00000000..f956416a
--- /dev/null
+++ b/src/gbox_sdk/resources/v1/models.py
@@ -0,0 +1,201 @@
+# 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: 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.
+ # 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: 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
+
+ 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: 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.
+ # 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: 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
+
+ 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/box_create_android_params.py b/src/gbox_sdk/types/v1/box_create_android_params.py
index 501f6f5c..54d59c69 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,22 @@ 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.
+
+ 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
+ """
+
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..e2a56aa0 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["container", "vm"], PropertyInfo(alias="deviceType")]
+ """Device type - container or vm Linux device"""
+
envs: Dict[str, str]
"""Environment variables for the box.
@@ -44,6 +47,22 @@ 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.
+
+ 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
+ """
+
labels: Dict[str, str]
"""Key-value pairs of labels for the box.
@@ -52,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 f4f90ea6..22282518 100644
--- a/src/gbox_sdk/types/v1/boxes/__init__.py
+++ b/src/gbox_sdk/types/v1/boxes/__init__.py
@@ -33,17 +33,27 @@
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 .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
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
@@ -56,6 +66,8 @@
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
from .browser_get_tabs_response import BrowserGetTabsResponse as BrowserGetTabsResponse
@@ -64,6 +76,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
@@ -71,6 +84,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/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
new file mode 100644
index 00000000..aa9e8278
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/action_click_response.py
@@ -0,0 +1,197 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List, 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"""
+
+ 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
+ """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/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..6619646e
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/android_appium_url_response.py
@@ -0,0 +1,54 @@
+# 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__ = ["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: 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"""
+
+ url: str
+ """Appium connection URL"""
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..6ef24683
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_create_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__ = ["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"""
+
+ 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
new file mode 100644
index 00000000..53dcf2a2
--- /dev/null
+++ b/src/gbox_sdk/types/v1/boxes/snapshot_list_response.py
@@ -0,0 +1,41 @@
+# 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"""
+
+ status: Literal["Pending", "Available", "Error"]
+ """The status 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/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/src/gbox_sdk/types/v1/linux_box.py b/src/gbox_sdk/types/v1/linux_box.py
index 029f50a0..44c050e7 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,6 +57,15 @@ class Config(BaseModel):
specified otherwise.
"""
+ 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/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..2011e5c7
--- /dev/null
+++ b/src/gbox_sdk/types/v1/model_call_params.py
@@ -0,0 +1,58 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing import Union
+from typing_extensions import Literal, Required, TypeAlias, TypedDict
+
+__all__ = ["ModelCallParams", "Action", "ActionClickAction", "ActionDragAction", "ActionScrollAction"]
+
+
+class ModelCallParams(TypedDict, total=False):
+ action: Required[Action]
+ """Structured action object (click or drag)"""
+
+ screenshot: Required[str]
+ """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"""
+
+
+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
new file mode 100644
index 00000000..c598f835
--- /dev/null
+++ b/src/gbox_sdk/types/v1/model_call_response.py
@@ -0,0 +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",
+ "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: Response
+ """Model response data"""
diff --git a/tests/api_resources/v1/boxes/test_actions.py b/tests/api_resources/v1/boxes/test_actions.py
index 48a0888f..85e7a32c 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
@@ -49,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": {
@@ -62,7 +70,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 +84,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 +98,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 +119,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
@@ -122,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": {
@@ -135,7 +144,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 +157,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 +170,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 +205,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
@@ -222,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": {
@@ -235,7 +245,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 +273,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 +301,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 +431,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 +461,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 +481,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 +501,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 +541,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 +577,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 +603,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 +629,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 +773,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 +797,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 +811,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 +825,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 +846,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 +869,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 +882,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 +895,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 +930,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 +968,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 +996,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 +1024,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 +1664,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 +1689,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 +1705,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 +1721,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 +1744,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 +1769,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 +1782,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 +1795,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 +1945,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 +1970,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 +1983,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 +1996,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 +2023,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 +2053,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 +2073,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 +2093,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 +2121,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 +2144,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 +2158,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 +2172,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 +2193,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 +2215,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 +2228,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 +2241,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 +2276,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 +2313,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 +2341,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 +2369,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 +2411,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 +2446,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 +2466,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 +2486,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 +2590,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
@@ -2592,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": {
@@ -2605,7 +2616,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 +2630,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 +2644,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 +2665,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
@@ -2665,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": {
@@ -2678,7 +2690,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 +2703,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 +2716,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 +2751,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
@@ -2765,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": {
@@ -2778,7 +2791,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 +2819,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 +2847,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 +2977,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 +3007,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 +3027,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 +3047,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 +3087,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 +3123,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 +3149,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 +3175,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 +3319,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 +3343,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 +3357,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 +3371,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 +3392,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 +3415,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 +3428,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 +3441,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 +3476,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 +3514,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 +3542,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 +3570,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 +4210,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 +4235,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 +4251,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 +4267,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 +4290,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 +4315,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 +4328,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 +4341,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 +4491,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 +4516,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 +4529,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 +4542,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 +4569,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 +4599,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 +4619,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 +4639,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 +4667,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 +4690,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 +4704,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 +4718,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 +4739,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 +4761,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 +4774,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 +4787,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 +4822,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 +4859,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 +4887,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 +4915,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 +4957,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 +4992,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 +5012,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 +5032,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
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:
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..343fa976
--- /dev/null
+++ b/tests/api_resources/v1/boxes/test_snapshot.py
@@ -0,0 +1,362 @@
+# 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 (
+ SnapshotGetResponse,
+ 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_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:
+ 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
+
+ @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(
+ "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_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:
+ 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
+
+ @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 e77083e5..4caf20d8 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",
@@ -174,15 +175,18 @@ 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": "container",
"envs": {
"DEBUG": "true",
"API_URL": "https://api.example.com",
},
"expires_in": "60m",
+ "keep_alive": "0ms",
"labels": {
"project": "web-automation",
"environment": "testing",
},
+ "snapshot_name": "snapshotName",
},
api_timeout="30s",
wait=True,
@@ -830,6 +834,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",
@@ -873,15 +878,18 @@ 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": "container",
"envs": {
"DEBUG": "true",
"API_URL": "https://api.example.com",
},
"expires_in": "60m",
+ "keep_alive": "0ms",
"labels": {
"project": "web-automation",
"environment": "testing",
},
+ "snapshot_name": "snapshotName",
},
api_timeout="30s",
wait=True,
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"
diff --git a/tests/api_resources/v1/test_models.py b/tests/api_resources/v1/test_models.py
new file mode 100644
index 00000000..99c17297
--- /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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ )
+ 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ )
+
+ 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ ) 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ )
+ 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ )
+
+ 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={
+ "target": "the VSCode app icon on the bottom dock",
+ "type": "click",
+ },
+ screenshot="screenshot",
+ ) 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
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
)
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")