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 @@ [![PyPI version](https://img.shields.io/pypi/v/gbox_sdk.svg?label=pypi%20(stable))](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")