From eb86c27107781172bf740e0af3833eee86b7cb60 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 20:31:16 +0000 Subject: [PATCH 1/3] feat: Phani/deploy with GitHub url --- .stats.yml | 6 +- README.md | 1 - api.md | 6 +- src/kernel/resources/deployments.py | 28 +- src/kernel/resources/extensions.py | 304 ++++++++-------- src/kernel/types/__init__.py | 4 +- src/kernel/types/deployment_create_params.py | 41 ++- ...d_params.py => extension_create_params.py} | 4 +- ...sponse.py => extension_create_response.py} | 4 +- tests/api_resources/test_deployments.py | 56 +-- tests/api_resources/test_extensions.py | 324 +++++++++--------- 11 files changed, 410 insertions(+), 368 deletions(-) rename src/kernel/types/{extension_upload_params.py => extension_create_params.py} (81%) rename src/kernel/types/{extension_upload_response.py => extension_create_response.py} (88%) diff --git a/.stats.yml b/.stats.yml index 6bb4af83..2bd40ccf 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 57 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6c765f1c4ce1c4dd4ceb371f56bf047aa79af36031ba43cbd68fa16a5fdb9bb3.yml -openapi_spec_hash: e9086f69281360f4e0895c9274a59531 -config_hash: deadfc4d2b0a947673bcf559b5db6e1b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6eaa6f5654abc94549962d7db1e8c7936af1f815bb3abe2f8249959394da1278.yml +openapi_spec_hash: 31ece7cd801e74228b80a8112a762e56 +config_hash: 3fc2057ce765bc5f27785a694ed0f553 diff --git a/README.md b/README.md index beec3f01..ae0066ec 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,6 @@ from kernel import Kernel client = Kernel() client.deployments.create( - entrypoint_rel_path="src/app.py", file=Path("/path/to/file"), ) ``` diff --git a/api.md b/api.md index 6059a78d..9f219359 100644 --- a/api.md +++ b/api.md @@ -202,13 +202,13 @@ Methods: Types: ```python -from kernel.types import ExtensionListResponse, ExtensionUploadResponse +from kernel.types import ExtensionCreateResponse, ExtensionListResponse ``` Methods: +- client.extensions.create(\*\*params) -> ExtensionCreateResponse +- client.extensions.retrieve(id_or_name) -> BinaryAPIResponse - client.extensions.list() -> ExtensionListResponse - client.extensions.delete(id_or_name) -> None -- client.extensions.download(id_or_name) -> BinaryAPIResponse - client.extensions.download_from_chrome_store(\*\*params) -> BinaryAPIResponse -- client.extensions.upload(\*\*params) -> ExtensionUploadResponse diff --git a/src/kernel/resources/deployments.py b/src/kernel/resources/deployments.py index 15812440..bdc200f1 100644 --- a/src/kernel/resources/deployments.py +++ b/src/kernel/resources/deployments.py @@ -52,11 +52,12 @@ def with_streaming_response(self) -> DeploymentsResourceWithStreamingResponse: def create( self, *, - entrypoint_rel_path: str, - file: FileTypes, + entrypoint_rel_path: str | Omit = omit, env_vars: Dict[str, str] | Omit = omit, + file: FileTypes | Omit = omit, force: bool | Omit = omit, region: Literal["aws.us-east-1a"] | Omit = omit, + source: deployment_create_params.Source | Omit = omit, version: 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. @@ -71,15 +72,17 @@ def create( Args: entrypoint_rel_path: Relative path to the entrypoint of the application - file: ZIP file containing the application source directory - env_vars: Map of environment variables to set for the deployed application. Each key-value pair represents an environment variable. + file: ZIP file containing the application source directory + force: Allow overwriting an existing app version region: Region for deployment. Currently we only support "aws.us-east-1a" + source: Source from which to fetch application code. + version: Version of the application. Can be any string. extra_headers: Send extra headers @@ -93,10 +96,11 @@ def create( body = deepcopy_minimal( { "entrypoint_rel_path": entrypoint_rel_path, - "file": file, "env_vars": env_vars, + "file": file, "force": force, "region": region, + "source": source, "version": version, } ) @@ -271,11 +275,12 @@ def with_streaming_response(self) -> AsyncDeploymentsResourceWithStreamingRespon async def create( self, *, - entrypoint_rel_path: str, - file: FileTypes, + entrypoint_rel_path: str | Omit = omit, env_vars: Dict[str, str] | Omit = omit, + file: FileTypes | Omit = omit, force: bool | Omit = omit, region: Literal["aws.us-east-1a"] | Omit = omit, + source: deployment_create_params.Source | Omit = omit, version: 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. @@ -290,15 +295,17 @@ async def create( Args: entrypoint_rel_path: Relative path to the entrypoint of the application - file: ZIP file containing the application source directory - env_vars: Map of environment variables to set for the deployed application. Each key-value pair represents an environment variable. + file: ZIP file containing the application source directory + force: Allow overwriting an existing app version region: Region for deployment. Currently we only support "aws.us-east-1a" + source: Source from which to fetch application code. + version: Version of the application. Can be any string. extra_headers: Send extra headers @@ -312,10 +319,11 @@ async def create( body = deepcopy_minimal( { "entrypoint_rel_path": entrypoint_rel_path, - "file": file, "env_vars": env_vars, + "file": file, "force": force, "region": region, + "source": source, "version": version, } ) diff --git a/src/kernel/resources/extensions.py b/src/kernel/resources/extensions.py index 2f868716..45d08d91 100644 --- a/src/kernel/resources/extensions.py +++ b/src/kernel/resources/extensions.py @@ -7,7 +7,7 @@ import httpx -from ..types import extension_upload_params, extension_download_from_chrome_store_params +from ..types import extension_create_params, extension_download_from_chrome_store_params from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, FileTypes, omit, not_given from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from .._compat import cached_property @@ -28,7 +28,7 @@ ) from .._base_client import make_request_options from ..types.extension_list_response import ExtensionListResponse -from ..types.extension_upload_response import ExtensionUploadResponse +from ..types.extension_create_response import ExtensionCreateResponse __all__ = ["ExtensionsResource", "AsyncExtensionsResource"] @@ -53,26 +53,58 @@ def with_streaming_response(self) -> ExtensionsResourceWithStreamingResponse: """ return ExtensionsResourceWithStreamingResponse(self) - def list( + def create( self, *, + file: FileTypes, + name: 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, - ) -> ExtensionListResponse: - """List extensions owned by the caller's organization.""" - return self._get( + ) -> ExtensionCreateResponse: + """Upload a zip file containing an unpacked browser extension. + + Optionally provide a + unique name for later reference. + + Args: + file: ZIP file containing the browser extension. + + name: Optional unique name within the organization to reference this extension. + + 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 + """ + body = deepcopy_minimal( + { + "file": file, + "name": name, + } + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( "/extensions", + body=maybe_transform(body, extension_create_params.ExtensionCreateParams), + files=files, options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionListResponse, + cast_to=ExtensionCreateResponse, ) - def delete( + def retrieve( self, id_or_name: str, *, @@ -82,9 +114,9 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: + ) -> BinaryAPIResponse: """ - Delete an extension by its ID or by its name. + Download the extension as a ZIP archive by ID or name. Args: extra_headers: Send extra headers @@ -97,16 +129,35 @@ def delete( """ if not id_or_name: raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return self._get( f"/extensions/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=BinaryAPIResponse, + ) + + def list( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> ExtensionListResponse: + """List extensions owned by the caller's organization.""" + return self._get( + "/extensions", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExtensionListResponse, ) - def download( + def delete( self, id_or_name: str, *, @@ -116,9 +167,9 @@ def download( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> BinaryAPIResponse: + ) -> None: """ - Download the extension as a ZIP archive by ID or name. + Delete an extension by its ID or by its name. Args: extra_headers: Send extra headers @@ -131,13 +182,13 @@ def download( """ if not id_or_name: raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") - extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} - return self._get( + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( f"/extensions/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=BinaryAPIResponse, + cast_to=NoneType, ) def download_from_chrome_store( @@ -188,7 +239,28 @@ def download_from_chrome_store( cast_to=BinaryAPIResponse, ) - def upload( + +class AsyncExtensionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExtensionsResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncExtensionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExtensionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncExtensionsResourceWithStreamingResponse(self) + + async def create( self, *, file: FileTypes, @@ -199,7 +271,7 @@ def upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionUploadResponse: + ) -> ExtensionCreateResponse: """Upload a zip file containing an unpacked browser extension. Optionally provide a @@ -229,36 +301,49 @@ def upload( # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return self._post( + return await self._post( "/extensions", - body=maybe_transform(body, extension_upload_params.ExtensionUploadParams), + body=await async_maybe_transform(body, extension_create_params.ExtensionCreateParams), files=files, options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionUploadResponse, + cast_to=ExtensionCreateResponse, ) - -class AsyncExtensionsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncExtensionsResourceWithRawResponse: + async def retrieve( + self, + id_or_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, + ) -> AsyncBinaryAPIResponse: """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. + Download the extension as a ZIP archive by ID or name. - For more information, see https://www.github.com/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncExtensionsResourceWithRawResponse(self) + Args: + extra_headers: Send extra headers - @cached_property - def with_streaming_response(self) -> AsyncExtensionsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. + extra_query: Add additional query parameters to the request - For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds """ - return AsyncExtensionsResourceWithStreamingResponse(self) + if not id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return await self._get( + f"/extensions/{id_or_name}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AsyncBinaryAPIResponse, + ) async def list( self, @@ -313,40 +398,6 @@ async def delete( cast_to=NoneType, ) - async def download( - self, - id_or_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, - ) -> AsyncBinaryAPIResponse: - """ - Download the extension as a ZIP archive by ID or 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 id_or_name: - raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") - extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} - return await self._get( - f"/extensions/{id_or_name}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AsyncBinaryAPIResponse, - ) - async def download_from_chrome_store( self, *, @@ -395,145 +446,94 @@ async def download_from_chrome_store( cast_to=AsyncBinaryAPIResponse, ) - async def upload( - self, - *, - file: FileTypes, - name: 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, - ) -> ExtensionUploadResponse: - """Upload a zip file containing an unpacked browser extension. - - Optionally provide a - unique name for later reference. - - Args: - file: ZIP file containing the browser extension. - - name: Optional unique name within the organization to reference this extension. - - 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 - """ - body = deepcopy_minimal( - { - "file": file, - "name": name, - } - ) - files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return await self._post( - "/extensions", - body=await async_maybe_transform(body, extension_upload_params.ExtensionUploadParams), - files=files, - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ExtensionUploadResponse, - ) - class ExtensionsResourceWithRawResponse: def __init__(self, extensions: ExtensionsResource) -> None: self._extensions = extensions + self.create = to_raw_response_wrapper( + extensions.create, + ) + self.retrieve = to_custom_raw_response_wrapper( + extensions.retrieve, + BinaryAPIResponse, + ) self.list = to_raw_response_wrapper( extensions.list, ) self.delete = to_raw_response_wrapper( extensions.delete, ) - self.download = to_custom_raw_response_wrapper( - extensions.download, - BinaryAPIResponse, - ) self.download_from_chrome_store = to_custom_raw_response_wrapper( extensions.download_from_chrome_store, BinaryAPIResponse, ) - self.upload = to_raw_response_wrapper( - extensions.upload, - ) class AsyncExtensionsResourceWithRawResponse: def __init__(self, extensions: AsyncExtensionsResource) -> None: self._extensions = extensions + self.create = async_to_raw_response_wrapper( + extensions.create, + ) + self.retrieve = async_to_custom_raw_response_wrapper( + extensions.retrieve, + AsyncBinaryAPIResponse, + ) self.list = async_to_raw_response_wrapper( extensions.list, ) self.delete = async_to_raw_response_wrapper( extensions.delete, ) - self.download = async_to_custom_raw_response_wrapper( - extensions.download, - AsyncBinaryAPIResponse, - ) self.download_from_chrome_store = async_to_custom_raw_response_wrapper( extensions.download_from_chrome_store, AsyncBinaryAPIResponse, ) - self.upload = async_to_raw_response_wrapper( - extensions.upload, - ) class ExtensionsResourceWithStreamingResponse: def __init__(self, extensions: ExtensionsResource) -> None: self._extensions = extensions + self.create = to_streamed_response_wrapper( + extensions.create, + ) + self.retrieve = to_custom_streamed_response_wrapper( + extensions.retrieve, + StreamedBinaryAPIResponse, + ) self.list = to_streamed_response_wrapper( extensions.list, ) self.delete = to_streamed_response_wrapper( extensions.delete, ) - self.download = to_custom_streamed_response_wrapper( - extensions.download, - StreamedBinaryAPIResponse, - ) self.download_from_chrome_store = to_custom_streamed_response_wrapper( extensions.download_from_chrome_store, StreamedBinaryAPIResponse, ) - self.upload = to_streamed_response_wrapper( - extensions.upload, - ) class AsyncExtensionsResourceWithStreamingResponse: def __init__(self, extensions: AsyncExtensionsResource) -> None: self._extensions = extensions + self.create = async_to_streamed_response_wrapper( + extensions.create, + ) + self.retrieve = async_to_custom_streamed_response_wrapper( + extensions.retrieve, + AsyncStreamedBinaryAPIResponse, + ) self.list = async_to_streamed_response_wrapper( extensions.list, ) self.delete = async_to_streamed_response_wrapper( extensions.delete, ) - self.download = async_to_custom_streamed_response_wrapper( - extensions.download, - AsyncStreamedBinaryAPIResponse, - ) self.download_from_chrome_store = async_to_custom_streamed_response_wrapper( extensions.download_from_chrome_store, AsyncStreamedBinaryAPIResponse, ) - self.upload = async_to_streamed_response_wrapper( - extensions.upload, - ) diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index 6b49cf7f..2edc0bcd 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -27,8 +27,8 @@ from .invocation_list_params import InvocationListParams as InvocationListParams from .invocation_state_event import InvocationStateEvent as InvocationStateEvent from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse +from .extension_create_params import ExtensionCreateParams as ExtensionCreateParams from .extension_list_response import ExtensionListResponse as ExtensionListResponse -from .extension_upload_params import ExtensionUploadParams as ExtensionUploadParams from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse from .deployment_create_params import DeploymentCreateParams as DeploymentCreateParams from .deployment_follow_params import DeploymentFollowParams as DeploymentFollowParams @@ -39,7 +39,7 @@ from .invocation_update_params import InvocationUpdateParams as InvocationUpdateParams from .browser_persistence_param import BrowserPersistenceParam as BrowserPersistenceParam from .browser_retrieve_response import BrowserRetrieveResponse as BrowserRetrieveResponse -from .extension_upload_response import ExtensionUploadResponse as ExtensionUploadResponse +from .extension_create_response import ExtensionCreateResponse as ExtensionCreateResponse from .deployment_create_response import DeploymentCreateResponse as DeploymentCreateResponse from .deployment_follow_response import DeploymentFollowResponse as DeploymentFollowResponse from .invocation_create_response import InvocationCreateResponse as InvocationCreateResponse diff --git a/src/kernel/types/deployment_create_params.py b/src/kernel/types/deployment_create_params.py index 6701c0a8..16eb5702 100644 --- a/src/kernel/types/deployment_create_params.py +++ b/src/kernel/types/deployment_create_params.py @@ -7,27 +7,58 @@ from .._types import FileTypes -__all__ = ["DeploymentCreateParams"] +__all__ = ["DeploymentCreateParams", "Source", "SourceAuth"] class DeploymentCreateParams(TypedDict, total=False): - entrypoint_rel_path: Required[str] + entrypoint_rel_path: str """Relative path to the entrypoint of the application""" - file: Required[FileTypes] - """ZIP file containing the application source directory""" - env_vars: Dict[str, str] """Map of environment variables to set for the deployed application. Each key-value pair represents an environment variable. """ + file: FileTypes + """ZIP file containing the application source directory""" + force: bool """Allow overwriting an existing app version""" region: Literal["aws.us-east-1a"] """Region for deployment. Currently we only support "aws.us-east-1a" """ + source: Source + """Source from which to fetch application code.""" + version: str """Version of the application. Can be any string.""" + + +class SourceAuth(TypedDict, total=False): + token: Required[str] + """GitHub PAT or installation access token""" + + method: Required[Literal["github_token"]] + """Auth method""" + + +class Source(TypedDict, total=False): + entrypoint: Required[str] + """Relative path to the application entrypoint within the selected path.""" + + ref: Required[str] + """Git ref (branch, tag, or commit SHA) to fetch.""" + + type: Required[Literal["github"]] + """Source type identifier.""" + + url: Required[str] + """Base repository URL (without blob/tree suffixes).""" + + auth: SourceAuth + """Authentication for private repositories.""" + + path: str + """Path within the repo to deploy (omit to use repo root).""" diff --git a/src/kernel/types/extension_upload_params.py b/src/kernel/types/extension_create_params.py similarity index 81% rename from src/kernel/types/extension_upload_params.py rename to src/kernel/types/extension_create_params.py index d36dde31..6bb2b397 100644 --- a/src/kernel/types/extension_upload_params.py +++ b/src/kernel/types/extension_create_params.py @@ -6,10 +6,10 @@ from .._types import FileTypes -__all__ = ["ExtensionUploadParams"] +__all__ = ["ExtensionCreateParams"] -class ExtensionUploadParams(TypedDict, total=False): +class ExtensionCreateParams(TypedDict, total=False): file: Required[FileTypes] """ZIP file containing the browser extension.""" diff --git a/src/kernel/types/extension_upload_response.py b/src/kernel/types/extension_create_response.py similarity index 88% rename from src/kernel/types/extension_upload_response.py rename to src/kernel/types/extension_create_response.py index 373e8861..c4fd6301 100644 --- a/src/kernel/types/extension_upload_response.py +++ b/src/kernel/types/extension_create_response.py @@ -5,10 +5,10 @@ from .._models import BaseModel -__all__ = ["ExtensionUploadResponse"] +__all__ = ["ExtensionCreateResponse"] -class ExtensionUploadResponse(BaseModel): +class ExtensionCreateResponse(BaseModel): id: str """Unique identifier for the extension""" diff --git a/tests/api_resources/test_deployments.py b/tests/api_resources/test_deployments.py index fc5d2991..6c3354ef 100644 --- a/tests/api_resources/test_deployments.py +++ b/tests/api_resources/test_deployments.py @@ -25,10 +25,7 @@ class TestDeployments: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_create(self, client: Kernel) -> None: - deployment = client.deployments.create( - entrypoint_rel_path="src/app.py", - file=b"raw file contents", - ) + deployment = client.deployments.create() assert_matches_type(DeploymentCreateResponse, deployment, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @@ -36,10 +33,21 @@ def test_method_create(self, client: Kernel) -> None: def test_method_create_with_all_params(self, client: Kernel) -> None: deployment = client.deployments.create( entrypoint_rel_path="src/app.py", + env_vars={"FOO": "bar"}, file=b"raw file contents", - env_vars={"foo": "string"}, force=False, region="aws.us-east-1a", + source={ + "entrypoint": "src/index.ts", + "ref": "main", + "type": "github", + "url": "https://github.com/org/repo", + "auth": { + "token": "ghs_***", + "method": "github_token", + }, + "path": "apps/api", + }, version="1.0.0", ) assert_matches_type(DeploymentCreateResponse, deployment, path=["response"]) @@ -47,10 +55,7 @@ def test_method_create_with_all_params(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_raw_response_create(self, client: Kernel) -> None: - response = client.deployments.with_raw_response.create( - entrypoint_rel_path="src/app.py", - file=b"raw file contents", - ) + response = client.deployments.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -60,10 +65,7 @@ def test_raw_response_create(self, client: Kernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_streaming_response_create(self, client: Kernel) -> None: - with client.deployments.with_streaming_response.create( - entrypoint_rel_path="src/app.py", - file=b"raw file contents", - ) as response: + with client.deployments.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -211,10 +213,7 @@ class TestAsyncDeployments: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_create(self, async_client: AsyncKernel) -> None: - deployment = await async_client.deployments.create( - entrypoint_rel_path="src/app.py", - file=b"raw file contents", - ) + deployment = await async_client.deployments.create() assert_matches_type(DeploymentCreateResponse, deployment, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @@ -222,10 +221,21 @@ async def test_method_create(self, async_client: AsyncKernel) -> None: async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: deployment = await async_client.deployments.create( entrypoint_rel_path="src/app.py", + env_vars={"FOO": "bar"}, file=b"raw file contents", - env_vars={"foo": "string"}, force=False, region="aws.us-east-1a", + source={ + "entrypoint": "src/index.ts", + "ref": "main", + "type": "github", + "url": "https://github.com/org/repo", + "auth": { + "token": "ghs_***", + "method": "github_token", + }, + "path": "apps/api", + }, version="1.0.0", ) assert_matches_type(DeploymentCreateResponse, deployment, path=["response"]) @@ -233,10 +243,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_raw_response_create(self, async_client: AsyncKernel) -> None: - response = await async_client.deployments.with_raw_response.create( - entrypoint_rel_path="src/app.py", - file=b"raw file contents", - ) + response = await async_client.deployments.with_raw_response.create() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -246,10 +253,7 @@ async def test_raw_response_create(self, async_client: AsyncKernel) -> None: @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_streaming_response_create(self, async_client: AsyncKernel) -> None: - async with async_client.deployments.with_streaming_response.create( - entrypoint_rel_path="src/app.py", - file=b"raw file contents", - ) as response: + async with async_client.deployments.with_streaming_response.create() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py index 5d61f327..ffdb02f6 100644 --- a/tests/api_resources/test_extensions.py +++ b/tests/api_resources/test_extensions.py @@ -13,7 +13,7 @@ from tests.utils import assert_matches_type from kernel.types import ( ExtensionListResponse, - ExtensionUploadResponse, + ExtensionCreateResponse, ) from kernel._response import ( BinaryAPIResponse, @@ -28,6 +28,99 @@ class TestExtensions: 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: Kernel) -> None: + extension = client.extensions.create( + file=b"raw file contents", + ) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: Kernel) -> None: + extension = client.extensions.create( + file=b"raw file contents", + name="name", + ) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_create(self, client: Kernel) -> None: + response = client.extensions.with_raw_response.create( + file=b"raw file contents", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + extension = response.parse() + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_create(self, client: Kernel) -> None: + with client.extensions.with_streaming_response.create( + file=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + extension = response.parse() + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_retrieve(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + extension = client.extensions.retrieve( + "id_or_name", + ) + assert extension.is_closed + assert extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_retrieve(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + extension = client.extensions.with_raw_response.retrieve( + "id_or_name", + ) + + assert extension.is_closed is True + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + assert extension.json() == {"foo": "bar"} + assert isinstance(extension, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_retrieve(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.extensions.with_streaming_response.retrieve( + "id_or_name", + ) as extension: + assert not extension.is_closed + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + + assert extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, StreamedBinaryAPIResponse) + + assert cast(Any, extension.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_retrieve(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): + client.extensions.with_raw_response.retrieve( + "", + ) + @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Kernel) -> None: @@ -98,56 +191,6 @@ def test_path_params_delete(self, client: Kernel) -> None: "", ) - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_download(self, client: Kernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - extension = client.extensions.download( - "id_or_name", - ) - assert extension.is_closed - assert extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - extension = client.extensions.with_raw_response.download( - "id_or_name", - ) - - assert extension.is_closed is True - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - assert extension.json() == {"foo": "bar"} - assert isinstance(extension, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_download(self, client: Kernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - with client.extensions.with_streaming_response.download( - "id_or_name", - ) as extension: - assert not extension.is_closed - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - - assert extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, StreamedBinaryAPIResponse) - - assert cast(Any, extension.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_path_params_download(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): - client.extensions.with_raw_response.download( - "", - ) - @parametrize @pytest.mark.respx(base_url=base_url) def test_method_download_from_chrome_store(self, client: Kernel, respx_mock: MockRouter) -> None: @@ -203,54 +246,104 @@ def test_streaming_response_download_from_chrome_store(self, client: Kernel, res assert cast(Any, extension.is_closed) is True + +class TestAsyncExtensions: + 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 - def test_method_upload(self, client: Kernel) -> None: - extension = client.extensions.upload( + async def test_method_create(self, async_client: AsyncKernel) -> None: + extension = await async_client.extensions.create( file=b"raw file contents", ) - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_method_upload_with_all_params(self, client: Kernel) -> None: - extension = client.extensions.upload( + async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: + extension = await async_client.extensions.create( file=b"raw file contents", name="name", ) - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_raw_response_upload(self, client: Kernel) -> None: - response = client.extensions.with_raw_response.upload( + async def test_raw_response_create(self, async_client: AsyncKernel) -> None: + response = await async_client.extensions.with_raw_response.create( file=b"raw file contents", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - extension = response.parse() - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + extension = await response.parse() + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - def test_streaming_response_upload(self, client: Kernel) -> None: - with client.extensions.with_streaming_response.upload( + async def test_streaming_response_create(self, async_client: AsyncKernel) -> None: + async with async_client.extensions.with_streaming_response.create( file=b"raw file contents", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - extension = response.parse() - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + extension = await response.parse() + assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_retrieve(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + extension = await async_client.extensions.retrieve( + "id_or_name", + ) + assert extension.is_closed + assert await extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, AsyncBinaryAPIResponse) -class TestAsyncExtensions: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_retrieve(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + extension = await async_client.extensions.with_raw_response.retrieve( + "id_or_name", + ) + + assert extension.is_closed is True + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + assert await extension.json() == {"foo": "bar"} + assert isinstance(extension, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_retrieve(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.extensions.with_streaming_response.retrieve( + "id_or_name", + ) as extension: + assert not extension.is_closed + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, extension.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): + await async_client.extensions.with_raw_response.retrieve( + "", + ) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -322,56 +415,6 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None: "", ) - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - extension = await async_client.extensions.download( - "id_or_name", - ) - assert extension.is_closed - assert await extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - extension = await async_client.extensions.with_raw_response.download( - "id_or_name", - ) - - assert extension.is_closed is True - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - assert await extension.json() == {"foo": "bar"} - assert isinstance(extension, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - async with async_client.extensions.with_streaming_response.download( - "id_or_name", - ) as extension: - assert not extension.is_closed - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - - assert await extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, AsyncStreamedBinaryAPIResponse) - - assert cast(Any, extension.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_path_params_download(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): - await async_client.extensions.with_raw_response.download( - "", - ) - @parametrize @pytest.mark.respx(base_url=base_url) async def test_method_download_from_chrome_store(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: @@ -432,46 +475,3 @@ async def test_streaming_response_download_from_chrome_store( assert isinstance(extension, AsyncStreamedBinaryAPIResponse) assert cast(Any, extension.is_closed) is True - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_upload(self, async_client: AsyncKernel) -> None: - extension = await async_client.extensions.upload( - file=b"raw file contents", - ) - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_method_upload_with_all_params(self, async_client: AsyncKernel) -> None: - extension = await async_client.extensions.upload( - file=b"raw file contents", - name="name", - ) - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_raw_response_upload(self, async_client: AsyncKernel) -> None: - response = await async_client.extensions.with_raw_response.upload( - file=b"raw file contents", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - extension = await response.parse() - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - async def test_streaming_response_upload(self, async_client: AsyncKernel) -> None: - async with async_client.extensions.with_streaming_response.upload( - file=b"raw file contents", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - extension = await response.parse() - assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) - - assert cast(Any, response.is_closed) is True From b63f7adc7cf0da614a4fc3a9eed491f71f7ec742 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:33:29 +0000 Subject: [PATCH 2/3] feat: click mouse, move mouse, screenshot --- .stats.yml | 8 +- api.md | 18 +- src/kernel/resources/browsers/__init__.py | 14 + src/kernel/resources/browsers/browsers.py | 32 + src/kernel/resources/browsers/computer.py | 949 ++++++++++++++++++ src/kernel/resources/extensions.py | 304 +++--- src/kernel/types/__init__.py | 4 +- src/kernel/types/browsers/__init__.py | 7 + .../computer_capture_screenshot_params.py | 25 + .../browsers/computer_click_mouse_params.py | 29 + .../browsers/computer_drag_mouse_params.py | 36 + .../browsers/computer_move_mouse_params.py | 20 + .../browsers/computer_press_key_params.py | 28 + .../types/browsers/computer_scroll_params.py | 26 + .../browsers/computer_type_text_params.py | 15 + ...e_params.py => extension_upload_params.py} | 4 +- ...sponse.py => extension_upload_response.py} | 4 +- tests/api_resources/browsers/test_computer.py | 892 ++++++++++++++++ tests/api_resources/test_extensions.py | 324 +++--- 19 files changed, 2412 insertions(+), 327 deletions(-) create mode 100644 src/kernel/resources/browsers/computer.py create mode 100644 src/kernel/types/browsers/computer_capture_screenshot_params.py create mode 100644 src/kernel/types/browsers/computer_click_mouse_params.py create mode 100644 src/kernel/types/browsers/computer_drag_mouse_params.py create mode 100644 src/kernel/types/browsers/computer_move_mouse_params.py create mode 100644 src/kernel/types/browsers/computer_press_key_params.py create mode 100644 src/kernel/types/browsers/computer_scroll_params.py create mode 100644 src/kernel/types/browsers/computer_type_text_params.py rename src/kernel/types/{extension_create_params.py => extension_upload_params.py} (81%) rename src/kernel/types/{extension_create_response.py => extension_upload_response.py} (88%) create mode 100644 tests/api_resources/browsers/test_computer.py diff --git a/.stats.yml b/.stats.yml index 2bd40ccf..b4dc6064 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 57 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-6eaa6f5654abc94549962d7db1e8c7936af1f815bb3abe2f8249959394da1278.yml -openapi_spec_hash: 31ece7cd801e74228b80a8112a762e56 -config_hash: 3fc2057ce765bc5f27785a694ed0f553 +configured_endpoints: 64 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e21f0324774a1762bc2bba0da3a8a6b0d0e74720d7a1c83dec813f9e027fcf58.yml +openapi_spec_hash: f1b636abfd6cb8e7c2ba7ffb8e53b9ba +config_hash: 09a2df23048cb16689c9a390d9e5bc47 diff --git a/api.md b/api.md index 9f219359..858dbfd8 100644 --- a/api.md +++ b/api.md @@ -166,6 +166,18 @@ Methods: - client.browsers.logs.stream(id, \*\*params) -> LogEvent +## Computer + +Methods: + +- client.browsers.computer.capture_screenshot(id, \*\*params) -> BinaryAPIResponse +- client.browsers.computer.click_mouse(id, \*\*params) -> None +- client.browsers.computer.drag_mouse(id, \*\*params) -> None +- client.browsers.computer.move_mouse(id, \*\*params) -> None +- client.browsers.computer.press_key(id, \*\*params) -> None +- client.browsers.computer.scroll(id, \*\*params) -> None +- client.browsers.computer.type_text(id, \*\*params) -> None + # Profiles Types: @@ -202,13 +214,13 @@ Methods: Types: ```python -from kernel.types import ExtensionCreateResponse, ExtensionListResponse +from kernel.types import ExtensionListResponse, ExtensionUploadResponse ``` Methods: -- client.extensions.create(\*\*params) -> ExtensionCreateResponse -- client.extensions.retrieve(id_or_name) -> BinaryAPIResponse - client.extensions.list() -> ExtensionListResponse - client.extensions.delete(id_or_name) -> None +- client.extensions.download(id_or_name) -> BinaryAPIResponse - client.extensions.download_from_chrome_store(\*\*params) -> BinaryAPIResponse +- client.extensions.upload(\*\*params) -> ExtensionUploadResponse diff --git a/src/kernel/resources/browsers/__init__.py b/src/kernel/resources/browsers/__init__.py index 97c987e4..abcc8f78 100644 --- a/src/kernel/resources/browsers/__init__.py +++ b/src/kernel/resources/browsers/__init__.py @@ -40,6 +40,14 @@ BrowsersResourceWithStreamingResponse, AsyncBrowsersResourceWithStreamingResponse, ) +from .computer import ( + ComputerResource, + AsyncComputerResource, + ComputerResourceWithRawResponse, + AsyncComputerResourceWithRawResponse, + ComputerResourceWithStreamingResponse, + AsyncComputerResourceWithStreamingResponse, +) __all__ = [ "ReplaysResource", @@ -66,6 +74,12 @@ "AsyncLogsResourceWithRawResponse", "LogsResourceWithStreamingResponse", "AsyncLogsResourceWithStreamingResponse", + "ComputerResource", + "AsyncComputerResource", + "ComputerResourceWithRawResponse", + "AsyncComputerResourceWithRawResponse", + "ComputerResourceWithStreamingResponse", + "AsyncComputerResourceWithStreamingResponse", "BrowsersResource", "AsyncBrowsersResource", "BrowsersResourceWithRawResponse", diff --git a/src/kernel/resources/browsers/browsers.py b/src/kernel/resources/browsers/browsers.py index 1d444214..c65a738a 100644 --- a/src/kernel/resources/browsers/browsers.py +++ b/src/kernel/resources/browsers/browsers.py @@ -41,6 +41,14 @@ ) from ..._types import Body, Omit, Query, Headers, NoneType, NotGiven, omit, not_given from ..._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform +from .computer import ( + ComputerResource, + AsyncComputerResource, + ComputerResourceWithRawResponse, + AsyncComputerResourceWithRawResponse, + ComputerResourceWithStreamingResponse, + AsyncComputerResourceWithStreamingResponse, +) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( @@ -75,6 +83,10 @@ def process(self) -> ProcessResource: def logs(self) -> LogsResource: return LogsResource(self._client) + @cached_property + def computer(self) -> ComputerResource: + return ComputerResource(self._client) + @cached_property def with_raw_response(self) -> BrowsersResourceWithRawResponse: """ @@ -375,6 +387,10 @@ def process(self) -> AsyncProcessResource: def logs(self) -> AsyncLogsResource: return AsyncLogsResource(self._client) + @cached_property + def computer(self) -> AsyncComputerResource: + return AsyncComputerResource(self._client) + @cached_property def with_raw_response(self) -> AsyncBrowsersResourceWithRawResponse: """ @@ -699,6 +715,10 @@ def process(self) -> ProcessResourceWithRawResponse: def logs(self) -> LogsResourceWithRawResponse: return LogsResourceWithRawResponse(self._browsers.logs) + @cached_property + def computer(self) -> ComputerResourceWithRawResponse: + return ComputerResourceWithRawResponse(self._browsers.computer) + class AsyncBrowsersResourceWithRawResponse: def __init__(self, browsers: AsyncBrowsersResource) -> None: @@ -739,6 +759,10 @@ def process(self) -> AsyncProcessResourceWithRawResponse: def logs(self) -> AsyncLogsResourceWithRawResponse: return AsyncLogsResourceWithRawResponse(self._browsers.logs) + @cached_property + def computer(self) -> AsyncComputerResourceWithRawResponse: + return AsyncComputerResourceWithRawResponse(self._browsers.computer) + class BrowsersResourceWithStreamingResponse: def __init__(self, browsers: BrowsersResource) -> None: @@ -779,6 +803,10 @@ def process(self) -> ProcessResourceWithStreamingResponse: def logs(self) -> LogsResourceWithStreamingResponse: return LogsResourceWithStreamingResponse(self._browsers.logs) + @cached_property + def computer(self) -> ComputerResourceWithStreamingResponse: + return ComputerResourceWithStreamingResponse(self._browsers.computer) + class AsyncBrowsersResourceWithStreamingResponse: def __init__(self, browsers: AsyncBrowsersResource) -> None: @@ -818,3 +846,7 @@ def process(self) -> AsyncProcessResourceWithStreamingResponse: @cached_property def logs(self) -> AsyncLogsResourceWithStreamingResponse: return AsyncLogsResourceWithStreamingResponse(self._browsers.logs) + + @cached_property + def computer(self) -> AsyncComputerResourceWithStreamingResponse: + return AsyncComputerResourceWithStreamingResponse(self._browsers.computer) diff --git a/src/kernel/resources/browsers/computer.py b/src/kernel/resources/browsers/computer.py new file mode 100644 index 00000000..68cee420 --- /dev/null +++ b/src/kernel/resources/browsers/computer.py @@ -0,0 +1,949 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal + +import httpx + +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 +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + to_custom_raw_response_wrapper, + async_to_streamed_response_wrapper, + to_custom_streamed_response_wrapper, + async_to_custom_raw_response_wrapper, + async_to_custom_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.browsers import ( + computer_scroll_params, + computer_press_key_params, + computer_type_text_params, + computer_drag_mouse_params, + computer_move_mouse_params, + computer_click_mouse_params, + computer_capture_screenshot_params, +) + +__all__ = ["ComputerResource", "AsyncComputerResource"] + + +class ComputerResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ComputerResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return ComputerResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ComputerResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return ComputerResourceWithStreamingResponse(self) + + def capture_screenshot( + self, + id: str, + *, + region: computer_capture_screenshot_params.Region | 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, + ) -> BinaryAPIResponse: + """ + Capture a screenshot of the browser instance + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "image/png", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/screenshot", + body=maybe_transform( + {"region": region}, computer_capture_screenshot_params.ComputerCaptureScreenshotParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=BinaryAPIResponse, + ) + + def click_mouse( + self, + id: str, + *, + x: int, + y: int, + button: Literal["left", "right", "middle", "back", "forward"] | Omit = omit, + click_type: Literal["down", "up", "click"] | Omit = omit, + hold_keys: SequenceNotStr[str] | Omit = omit, + num_clicks: 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, + ) -> None: + """ + Simulate a mouse click action on the browser instance + + Args: + x: X coordinate of the click position + + y: Y coordinate of the click position + + button: Mouse button to interact with + + click_type: Type of click action + + hold_keys: Modifier keys to hold during the click + + num_clicks: Number of times to repeat the click + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/click_mouse", + body=maybe_transform( + { + "x": x, + "y": y, + "button": button, + "click_type": click_type, + "hold_keys": hold_keys, + "num_clicks": num_clicks, + }, + computer_click_mouse_params.ComputerClickMouseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def drag_mouse( + self, + id: str, + *, + path: Iterable[Iterable[int]], + button: Literal["left", "middle", "right"] | Omit = omit, + delay: int | Omit = omit, + hold_keys: SequenceNotStr[str] | Omit = omit, + step_delay_ms: int | Omit = omit, + steps_per_segment: 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, + ) -> None: + """ + Drag the mouse along a path + + Args: + path: Ordered list of [x, y] coordinate pairs to move through while dragging. Must + contain at least 2 points. + + button: Mouse button to drag with + + delay: Delay in milliseconds between button down and starting to move along the path. + + hold_keys: Modifier keys to hold during the drag + + step_delay_ms: Delay in milliseconds between relative steps while dragging (not the initial + delay). + + steps_per_segment: Number of relative move steps per segment in the path. Minimum 1. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/drag_mouse", + body=maybe_transform( + { + "path": path, + "button": button, + "delay": delay, + "hold_keys": hold_keys, + "step_delay_ms": step_delay_ms, + "steps_per_segment": steps_per_segment, + }, + computer_drag_mouse_params.ComputerDragMouseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def move_mouse( + self, + id: str, + *, + x: int, + y: int, + hold_keys: SequenceNotStr[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, + ) -> None: + """ + Move the mouse cursor to the specified coordinates on the browser instance + + Args: + x: X coordinate to move the cursor to + + y: Y coordinate to move the cursor to + + hold_keys: Modifier keys to hold during the move + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/move_mouse", + body=maybe_transform( + { + "x": x, + "y": y, + "hold_keys": hold_keys, + }, + computer_move_mouse_params.ComputerMoveMouseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def press_key( + self, + id: str, + *, + keys: SequenceNotStr[str], + duration: int | Omit = omit, + hold_keys: SequenceNotStr[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, + ) -> None: + """ + Press one or more keys on the host computer + + Args: + keys: List of key symbols to press. Each item should be a key symbol supported by + xdotool (see X11 keysym definitions). Examples include "Return", "Shift", + "Ctrl", "Alt", "F5". Items in this list could also be combinations, e.g. + "Ctrl+t" or "Ctrl+Shift+Tab". + + duration: Duration to hold the keys down in milliseconds. If omitted or 0, keys are + tapped. + + hold_keys: Optional modifier keys to hold during the key press sequence. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/press_key", + body=maybe_transform( + { + "keys": keys, + "duration": duration, + "hold_keys": hold_keys, + }, + computer_press_key_params.ComputerPressKeyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def scroll( + self, + id: str, + *, + x: int, + y: int, + delta_x: int | Omit = omit, + delta_y: int | Omit = omit, + hold_keys: SequenceNotStr[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, + ) -> None: + """ + Scroll the mouse wheel at a position on the host computer + + Args: + x: X coordinate at which to perform the scroll + + y: Y coordinate at which to perform the scroll + + delta_x: Horizontal scroll amount. Positive scrolls right, negative scrolls left. + + delta_y: Vertical scroll amount. Positive scrolls down, negative scrolls up. + + hold_keys: Modifier keys to hold during the scroll + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/scroll", + body=maybe_transform( + { + "x": x, + "y": y, + "delta_x": delta_x, + "delta_y": delta_y, + "hold_keys": hold_keys, + }, + computer_scroll_params.ComputerScrollParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + def type_text( + self, + id: str, + *, + text: str, + delay: 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, + ) -> None: + """ + Type text on the browser instance + + Args: + text: Text to type on the browser instance + + delay: Delay in milliseconds between keystrokes + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._post( + f"/browsers/{id}/computer/type", + body=maybe_transform( + { + "text": text, + "delay": delay, + }, + computer_type_text_params.ComputerTypeTextParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class AsyncComputerResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncComputerResourceWithRawResponse: + """ + 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncComputerResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncComputerResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response + """ + return AsyncComputerResourceWithStreamingResponse(self) + + async def capture_screenshot( + self, + id: str, + *, + region: computer_capture_screenshot_params.Region | 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, + ) -> AsyncBinaryAPIResponse: + """ + Capture a screenshot of the browser instance + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "image/png", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/screenshot", + body=await async_maybe_transform( + {"region": region}, computer_capture_screenshot_params.ComputerCaptureScreenshotParams + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AsyncBinaryAPIResponse, + ) + + async def click_mouse( + self, + id: str, + *, + x: int, + y: int, + button: Literal["left", "right", "middle", "back", "forward"] | Omit = omit, + click_type: Literal["down", "up", "click"] | Omit = omit, + hold_keys: SequenceNotStr[str] | Omit = omit, + num_clicks: 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, + ) -> None: + """ + Simulate a mouse click action on the browser instance + + Args: + x: X coordinate of the click position + + y: Y coordinate of the click position + + button: Mouse button to interact with + + click_type: Type of click action + + hold_keys: Modifier keys to hold during the click + + num_clicks: Number of times to repeat the click + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/click_mouse", + body=await async_maybe_transform( + { + "x": x, + "y": y, + "button": button, + "click_type": click_type, + "hold_keys": hold_keys, + "num_clicks": num_clicks, + }, + computer_click_mouse_params.ComputerClickMouseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def drag_mouse( + self, + id: str, + *, + path: Iterable[Iterable[int]], + button: Literal["left", "middle", "right"] | Omit = omit, + delay: int | Omit = omit, + hold_keys: SequenceNotStr[str] | Omit = omit, + step_delay_ms: int | Omit = omit, + steps_per_segment: 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, + ) -> None: + """ + Drag the mouse along a path + + Args: + path: Ordered list of [x, y] coordinate pairs to move through while dragging. Must + contain at least 2 points. + + button: Mouse button to drag with + + delay: Delay in milliseconds between button down and starting to move along the path. + + hold_keys: Modifier keys to hold during the drag + + step_delay_ms: Delay in milliseconds between relative steps while dragging (not the initial + delay). + + steps_per_segment: Number of relative move steps per segment in the path. Minimum 1. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/drag_mouse", + body=await async_maybe_transform( + { + "path": path, + "button": button, + "delay": delay, + "hold_keys": hold_keys, + "step_delay_ms": step_delay_ms, + "steps_per_segment": steps_per_segment, + }, + computer_drag_mouse_params.ComputerDragMouseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def move_mouse( + self, + id: str, + *, + x: int, + y: int, + hold_keys: SequenceNotStr[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, + ) -> None: + """ + Move the mouse cursor to the specified coordinates on the browser instance + + Args: + x: X coordinate to move the cursor to + + y: Y coordinate to move the cursor to + + hold_keys: Modifier keys to hold during the move + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/move_mouse", + body=await async_maybe_transform( + { + "x": x, + "y": y, + "hold_keys": hold_keys, + }, + computer_move_mouse_params.ComputerMoveMouseParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def press_key( + self, + id: str, + *, + keys: SequenceNotStr[str], + duration: int | Omit = omit, + hold_keys: SequenceNotStr[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, + ) -> None: + """ + Press one or more keys on the host computer + + Args: + keys: List of key symbols to press. Each item should be a key symbol supported by + xdotool (see X11 keysym definitions). Examples include "Return", "Shift", + "Ctrl", "Alt", "F5". Items in this list could also be combinations, e.g. + "Ctrl+t" or "Ctrl+Shift+Tab". + + duration: Duration to hold the keys down in milliseconds. If omitted or 0, keys are + tapped. + + hold_keys: Optional modifier keys to hold during the key press sequence. + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/press_key", + body=await async_maybe_transform( + { + "keys": keys, + "duration": duration, + "hold_keys": hold_keys, + }, + computer_press_key_params.ComputerPressKeyParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def scroll( + self, + id: str, + *, + x: int, + y: int, + delta_x: int | Omit = omit, + delta_y: int | Omit = omit, + hold_keys: SequenceNotStr[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, + ) -> None: + """ + Scroll the mouse wheel at a position on the host computer + + Args: + x: X coordinate at which to perform the scroll + + y: Y coordinate at which to perform the scroll + + delta_x: Horizontal scroll amount. Positive scrolls right, negative scrolls left. + + delta_y: Vertical scroll amount. Positive scrolls down, negative scrolls up. + + hold_keys: Modifier keys to hold during the scroll + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/scroll", + body=await async_maybe_transform( + { + "x": x, + "y": y, + "delta_x": delta_x, + "delta_y": delta_y, + "hold_keys": hold_keys, + }, + computer_scroll_params.ComputerScrollParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + async def type_text( + self, + id: str, + *, + text: str, + delay: 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, + ) -> None: + """ + Type text on the browser instance + + Args: + text: Text to type on the browser instance + + delay: Delay in milliseconds between keystrokes + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return await self._post( + f"/browsers/{id}/computer/type", + body=await async_maybe_transform( + { + "text": text, + "delay": delay, + }, + computer_type_text_params.ComputerTypeTextParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=NoneType, + ) + + +class ComputerResourceWithRawResponse: + def __init__(self, computer: ComputerResource) -> None: + self._computer = computer + + self.capture_screenshot = to_custom_raw_response_wrapper( + computer.capture_screenshot, + BinaryAPIResponse, + ) + self.click_mouse = to_raw_response_wrapper( + computer.click_mouse, + ) + self.drag_mouse = to_raw_response_wrapper( + computer.drag_mouse, + ) + self.move_mouse = to_raw_response_wrapper( + computer.move_mouse, + ) + self.press_key = to_raw_response_wrapper( + computer.press_key, + ) + self.scroll = to_raw_response_wrapper( + computer.scroll, + ) + self.type_text = to_raw_response_wrapper( + computer.type_text, + ) + + +class AsyncComputerResourceWithRawResponse: + def __init__(self, computer: AsyncComputerResource) -> None: + self._computer = computer + + self.capture_screenshot = async_to_custom_raw_response_wrapper( + computer.capture_screenshot, + AsyncBinaryAPIResponse, + ) + self.click_mouse = async_to_raw_response_wrapper( + computer.click_mouse, + ) + self.drag_mouse = async_to_raw_response_wrapper( + computer.drag_mouse, + ) + self.move_mouse = async_to_raw_response_wrapper( + computer.move_mouse, + ) + self.press_key = async_to_raw_response_wrapper( + computer.press_key, + ) + self.scroll = async_to_raw_response_wrapper( + computer.scroll, + ) + self.type_text = async_to_raw_response_wrapper( + computer.type_text, + ) + + +class ComputerResourceWithStreamingResponse: + def __init__(self, computer: ComputerResource) -> None: + self._computer = computer + + self.capture_screenshot = to_custom_streamed_response_wrapper( + computer.capture_screenshot, + StreamedBinaryAPIResponse, + ) + self.click_mouse = to_streamed_response_wrapper( + computer.click_mouse, + ) + self.drag_mouse = to_streamed_response_wrapper( + computer.drag_mouse, + ) + self.move_mouse = to_streamed_response_wrapper( + computer.move_mouse, + ) + self.press_key = to_streamed_response_wrapper( + computer.press_key, + ) + self.scroll = to_streamed_response_wrapper( + computer.scroll, + ) + self.type_text = to_streamed_response_wrapper( + computer.type_text, + ) + + +class AsyncComputerResourceWithStreamingResponse: + def __init__(self, computer: AsyncComputerResource) -> None: + self._computer = computer + + self.capture_screenshot = async_to_custom_streamed_response_wrapper( + computer.capture_screenshot, + AsyncStreamedBinaryAPIResponse, + ) + self.click_mouse = async_to_streamed_response_wrapper( + computer.click_mouse, + ) + self.drag_mouse = async_to_streamed_response_wrapper( + computer.drag_mouse, + ) + self.move_mouse = async_to_streamed_response_wrapper( + computer.move_mouse, + ) + self.press_key = async_to_streamed_response_wrapper( + computer.press_key, + ) + self.scroll = async_to_streamed_response_wrapper( + computer.scroll, + ) + self.type_text = async_to_streamed_response_wrapper( + computer.type_text, + ) diff --git a/src/kernel/resources/extensions.py b/src/kernel/resources/extensions.py index 45d08d91..2f868716 100644 --- a/src/kernel/resources/extensions.py +++ b/src/kernel/resources/extensions.py @@ -7,7 +7,7 @@ import httpx -from ..types import extension_create_params, extension_download_from_chrome_store_params +from ..types import extension_upload_params, extension_download_from_chrome_store_params from .._types import Body, Omit, Query, Headers, NoneType, NotGiven, FileTypes, omit, not_given from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from .._compat import cached_property @@ -28,7 +28,7 @@ ) from .._base_client import make_request_options from ..types.extension_list_response import ExtensionListResponse -from ..types.extension_create_response import ExtensionCreateResponse +from ..types.extension_upload_response import ExtensionUploadResponse __all__ = ["ExtensionsResource", "AsyncExtensionsResource"] @@ -53,58 +53,26 @@ def with_streaming_response(self) -> ExtensionsResourceWithStreamingResponse: """ return ExtensionsResourceWithStreamingResponse(self) - def create( + def list( self, *, - file: FileTypes, - name: 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, - ) -> ExtensionCreateResponse: - """Upload a zip file containing an unpacked browser extension. - - Optionally provide a - unique name for later reference. - - Args: - file: ZIP file containing the browser extension. - - name: Optional unique name within the organization to reference this extension. - - 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 - """ - body = deepcopy_minimal( - { - "file": file, - "name": name, - } - ) - files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return self._post( + ) -> ExtensionListResponse: + """List extensions owned by the caller's organization.""" + return self._get( "/extensions", - body=maybe_transform(body, extension_create_params.ExtensionCreateParams), - files=files, options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionCreateResponse, + cast_to=ExtensionListResponse, ) - def retrieve( + def delete( self, id_or_name: str, *, @@ -114,9 +82,9 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> BinaryAPIResponse: + ) -> None: """ - Download the extension as a ZIP archive by ID or name. + Delete an extension by its ID or by its name. Args: extra_headers: Send extra headers @@ -129,35 +97,16 @@ def retrieve( """ if not id_or_name: raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") - extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} - return self._get( + extra_headers = {"Accept": "*/*", **(extra_headers or {})} + return self._delete( f"/extensions/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=BinaryAPIResponse, - ) - - def list( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionListResponse: - """List extensions owned by the caller's organization.""" - return self._get( - "/extensions", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=ExtensionListResponse, + cast_to=NoneType, ) - def delete( + def download( self, id_or_name: str, *, @@ -167,9 +116,9 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: + ) -> BinaryAPIResponse: """ - Delete an extension by its ID or by its name. + Download the extension as a ZIP archive by ID or name. Args: extra_headers: Send extra headers @@ -182,13 +131,13 @@ def delete( """ if not id_or_name: raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} - return self._delete( + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return self._get( f"/extensions/{id_or_name}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=BinaryAPIResponse, ) def download_from_chrome_store( @@ -239,28 +188,7 @@ def download_from_chrome_store( cast_to=BinaryAPIResponse, ) - -class AsyncExtensionsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncExtensionsResourceWithRawResponse: - """ - 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/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncExtensionsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncExtensionsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response - """ - return AsyncExtensionsResourceWithStreamingResponse(self) - - async def create( + def upload( self, *, file: FileTypes, @@ -271,7 +199,7 @@ async def create( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ExtensionCreateResponse: + ) -> ExtensionUploadResponse: """Upload a zip file containing an unpacked browser extension. Optionally provide a @@ -301,49 +229,36 @@ async def create( # sent to the server will contain a `boundary` parameter, e.g. # multipart/form-data; boundary=---abc-- extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return await self._post( + return self._post( "/extensions", - body=await async_maybe_transform(body, extension_create_params.ExtensionCreateParams), + body=maybe_transform(body, extension_upload_params.ExtensionUploadParams), files=files, options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ExtensionCreateResponse, + cast_to=ExtensionUploadResponse, ) - async def retrieve( - self, - id_or_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, - ) -> AsyncBinaryAPIResponse: - """ - Download the extension as a ZIP archive by ID or name. - Args: - extra_headers: Send extra headers +class AsyncExtensionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExtensionsResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. - extra_query: Add additional query parameters to the request + For more information, see https://www.github.com/onkernel/kernel-python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncExtensionsResourceWithRawResponse(self) - extra_body: Add additional JSON properties to the request + @cached_property + def with_streaming_response(self) -> AsyncExtensionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. - timeout: Override the client-level default timeout for this request, in seconds + For more information, see https://www.github.com/onkernel/kernel-python-sdk#with_streaming_response """ - if not id_or_name: - raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") - extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} - return await self._get( - f"/extensions/{id_or_name}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=AsyncBinaryAPIResponse, - ) + return AsyncExtensionsResourceWithStreamingResponse(self) async def list( self, @@ -398,6 +313,40 @@ async def delete( cast_to=NoneType, ) + async def download( + self, + id_or_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, + ) -> AsyncBinaryAPIResponse: + """ + Download the extension as a ZIP archive by ID or 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 id_or_name: + raise ValueError(f"Expected a non-empty value for `id_or_name` but received {id_or_name!r}") + extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})} + return await self._get( + f"/extensions/{id_or_name}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=AsyncBinaryAPIResponse, + ) + async def download_from_chrome_store( self, *, @@ -446,94 +395,145 @@ async def download_from_chrome_store( cast_to=AsyncBinaryAPIResponse, ) + async def upload( + self, + *, + file: FileTypes, + name: 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, + ) -> ExtensionUploadResponse: + """Upload a zip file containing an unpacked browser extension. + + Optionally provide a + unique name for later reference. + + Args: + file: ZIP file containing the browser extension. + + name: Optional unique name within the organization to reference this extension. + + 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 + """ + body = deepcopy_minimal( + { + "file": file, + "name": name, + } + ) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/extensions", + body=await async_maybe_transform(body, extension_upload_params.ExtensionUploadParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ExtensionUploadResponse, + ) + class ExtensionsResourceWithRawResponse: def __init__(self, extensions: ExtensionsResource) -> None: self._extensions = extensions - self.create = to_raw_response_wrapper( - extensions.create, - ) - self.retrieve = to_custom_raw_response_wrapper( - extensions.retrieve, - BinaryAPIResponse, - ) self.list = to_raw_response_wrapper( extensions.list, ) self.delete = to_raw_response_wrapper( extensions.delete, ) + self.download = to_custom_raw_response_wrapper( + extensions.download, + BinaryAPIResponse, + ) self.download_from_chrome_store = to_custom_raw_response_wrapper( extensions.download_from_chrome_store, BinaryAPIResponse, ) + self.upload = to_raw_response_wrapper( + extensions.upload, + ) class AsyncExtensionsResourceWithRawResponse: def __init__(self, extensions: AsyncExtensionsResource) -> None: self._extensions = extensions - self.create = async_to_raw_response_wrapper( - extensions.create, - ) - self.retrieve = async_to_custom_raw_response_wrapper( - extensions.retrieve, - AsyncBinaryAPIResponse, - ) self.list = async_to_raw_response_wrapper( extensions.list, ) self.delete = async_to_raw_response_wrapper( extensions.delete, ) + self.download = async_to_custom_raw_response_wrapper( + extensions.download, + AsyncBinaryAPIResponse, + ) self.download_from_chrome_store = async_to_custom_raw_response_wrapper( extensions.download_from_chrome_store, AsyncBinaryAPIResponse, ) + self.upload = async_to_raw_response_wrapper( + extensions.upload, + ) class ExtensionsResourceWithStreamingResponse: def __init__(self, extensions: ExtensionsResource) -> None: self._extensions = extensions - self.create = to_streamed_response_wrapper( - extensions.create, - ) - self.retrieve = to_custom_streamed_response_wrapper( - extensions.retrieve, - StreamedBinaryAPIResponse, - ) self.list = to_streamed_response_wrapper( extensions.list, ) self.delete = to_streamed_response_wrapper( extensions.delete, ) + self.download = to_custom_streamed_response_wrapper( + extensions.download, + StreamedBinaryAPIResponse, + ) self.download_from_chrome_store = to_custom_streamed_response_wrapper( extensions.download_from_chrome_store, StreamedBinaryAPIResponse, ) + self.upload = to_streamed_response_wrapper( + extensions.upload, + ) class AsyncExtensionsResourceWithStreamingResponse: def __init__(self, extensions: AsyncExtensionsResource) -> None: self._extensions = extensions - self.create = async_to_streamed_response_wrapper( - extensions.create, - ) - self.retrieve = async_to_custom_streamed_response_wrapper( - extensions.retrieve, - AsyncStreamedBinaryAPIResponse, - ) self.list = async_to_streamed_response_wrapper( extensions.list, ) self.delete = async_to_streamed_response_wrapper( extensions.delete, ) + self.download = async_to_custom_streamed_response_wrapper( + extensions.download, + AsyncStreamedBinaryAPIResponse, + ) self.download_from_chrome_store = async_to_custom_streamed_response_wrapper( extensions.download_from_chrome_store, AsyncStreamedBinaryAPIResponse, ) + self.upload = async_to_streamed_response_wrapper( + extensions.upload, + ) diff --git a/src/kernel/types/__init__.py b/src/kernel/types/__init__.py index 2edc0bcd..6b49cf7f 100644 --- a/src/kernel/types/__init__.py +++ b/src/kernel/types/__init__.py @@ -27,8 +27,8 @@ from .invocation_list_params import InvocationListParams as InvocationListParams from .invocation_state_event import InvocationStateEvent as InvocationStateEvent from .browser_create_response import BrowserCreateResponse as BrowserCreateResponse -from .extension_create_params import ExtensionCreateParams as ExtensionCreateParams from .extension_list_response import ExtensionListResponse as ExtensionListResponse +from .extension_upload_params import ExtensionUploadParams as ExtensionUploadParams from .proxy_retrieve_response import ProxyRetrieveResponse as ProxyRetrieveResponse from .deployment_create_params import DeploymentCreateParams as DeploymentCreateParams from .deployment_follow_params import DeploymentFollowParams as DeploymentFollowParams @@ -39,7 +39,7 @@ from .invocation_update_params import InvocationUpdateParams as InvocationUpdateParams from .browser_persistence_param import BrowserPersistenceParam as BrowserPersistenceParam from .browser_retrieve_response import BrowserRetrieveResponse as BrowserRetrieveResponse -from .extension_create_response import ExtensionCreateResponse as ExtensionCreateResponse +from .extension_upload_response import ExtensionUploadResponse as ExtensionUploadResponse from .deployment_create_response import DeploymentCreateResponse as DeploymentCreateResponse from .deployment_follow_response import DeploymentFollowResponse as DeploymentFollowResponse from .invocation_create_response import InvocationCreateResponse as InvocationCreateResponse diff --git a/src/kernel/types/browsers/__init__.py b/src/kernel/types/browsers/__init__.py index d0b6b383..9b0ed53a 100644 --- a/src/kernel/types/browsers/__init__.py +++ b/src/kernel/types/browsers/__init__.py @@ -22,11 +22,18 @@ from .process_exec_response import ProcessExecResponse as ProcessExecResponse from .process_kill_response import ProcessKillResponse as ProcessKillResponse from .replay_start_response import ReplayStartResponse as ReplayStartResponse +from .computer_scroll_params import ComputerScrollParams as ComputerScrollParams from .process_spawn_response import ProcessSpawnResponse as ProcessSpawnResponse from .process_stdin_response import ProcessStdinResponse as ProcessStdinResponse from .process_status_response import ProcessStatusResponse as ProcessStatusResponse +from .computer_press_key_params import ComputerPressKeyParams as ComputerPressKeyParams +from .computer_type_text_params import ComputerTypeTextParams as ComputerTypeTextParams from .f_create_directory_params import FCreateDirectoryParams as FCreateDirectoryParams from .f_delete_directory_params import FDeleteDirectoryParams as FDeleteDirectoryParams from .f_download_dir_zip_params import FDownloadDirZipParams as FDownloadDirZipParams +from .computer_drag_mouse_params import ComputerDragMouseParams as ComputerDragMouseParams +from .computer_move_mouse_params import ComputerMoveMouseParams as ComputerMoveMouseParams +from .computer_click_mouse_params import ComputerClickMouseParams as ComputerClickMouseParams from .f_set_file_permissions_params import FSetFilePermissionsParams as FSetFilePermissionsParams from .process_stdout_stream_response import ProcessStdoutStreamResponse as ProcessStdoutStreamResponse +from .computer_capture_screenshot_params import ComputerCaptureScreenshotParams as ComputerCaptureScreenshotParams diff --git a/src/kernel/types/browsers/computer_capture_screenshot_params.py b/src/kernel/types/browsers/computer_capture_screenshot_params.py new file mode 100644 index 00000000..942cef30 --- /dev/null +++ b/src/kernel/types/browsers/computer_capture_screenshot_params.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ComputerCaptureScreenshotParams", "Region"] + + +class ComputerCaptureScreenshotParams(TypedDict, total=False): + region: Region + + +class Region(TypedDict, total=False): + height: Required[int] + """Height of the region in pixels""" + + width: Required[int] + """Width of the region in pixels""" + + x: Required[int] + """X coordinate of the region's top-left corner""" + + y: Required[int] + """Y coordinate of the region's top-left corner""" diff --git a/src/kernel/types/browsers/computer_click_mouse_params.py b/src/kernel/types/browsers/computer_click_mouse_params.py new file mode 100644 index 00000000..9bde2e6a --- /dev/null +++ b/src/kernel/types/browsers/computer_click_mouse_params.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ComputerClickMouseParams"] + + +class ComputerClickMouseParams(TypedDict, total=False): + x: Required[int] + """X coordinate of the click position""" + + y: Required[int] + """Y coordinate of the click position""" + + button: Literal["left", "right", "middle", "back", "forward"] + """Mouse button to interact with""" + + click_type: Literal["down", "up", "click"] + """Type of click action""" + + hold_keys: SequenceNotStr[str] + """Modifier keys to hold during the click""" + + num_clicks: int + """Number of times to repeat the click""" diff --git a/src/kernel/types/browsers/computer_drag_mouse_params.py b/src/kernel/types/browsers/computer_drag_mouse_params.py new file mode 100644 index 00000000..fb03b4be --- /dev/null +++ b/src/kernel/types/browsers/computer_drag_mouse_params.py @@ -0,0 +1,36 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Literal, Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ComputerDragMouseParams"] + + +class ComputerDragMouseParams(TypedDict, total=False): + path: Required[Iterable[Iterable[int]]] + """Ordered list of [x, y] coordinate pairs to move through while dragging. + + Must contain at least 2 points. + """ + + button: Literal["left", "middle", "right"] + """Mouse button to drag with""" + + delay: int + """Delay in milliseconds between button down and starting to move along the path.""" + + hold_keys: SequenceNotStr[str] + """Modifier keys to hold during the drag""" + + step_delay_ms: int + """ + Delay in milliseconds between relative steps while dragging (not the initial + delay). + """ + + steps_per_segment: int + """Number of relative move steps per segment in the path. Minimum 1.""" diff --git a/src/kernel/types/browsers/computer_move_mouse_params.py b/src/kernel/types/browsers/computer_move_mouse_params.py new file mode 100644 index 00000000..1769e074 --- /dev/null +++ b/src/kernel/types/browsers/computer_move_mouse_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ComputerMoveMouseParams"] + + +class ComputerMoveMouseParams(TypedDict, total=False): + x: Required[int] + """X coordinate to move the cursor to""" + + y: Required[int] + """Y coordinate to move the cursor to""" + + hold_keys: SequenceNotStr[str] + """Modifier keys to hold during the move""" diff --git a/src/kernel/types/browsers/computer_press_key_params.py b/src/kernel/types/browsers/computer_press_key_params.py new file mode 100644 index 00000000..ea2c9b45 --- /dev/null +++ b/src/kernel/types/browsers/computer_press_key_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ComputerPressKeyParams"] + + +class ComputerPressKeyParams(TypedDict, total=False): + keys: Required[SequenceNotStr[str]] + """List of key symbols to press. + + Each item should be a key symbol supported by xdotool (see X11 keysym + definitions). Examples include "Return", "Shift", "Ctrl", "Alt", "F5". Items in + this list could also be combinations, e.g. "Ctrl+t" or "Ctrl+Shift+Tab". + """ + + duration: int + """Duration to hold the keys down in milliseconds. + + If omitted or 0, keys are tapped. + """ + + hold_keys: SequenceNotStr[str] + """Optional modifier keys to hold during the key press sequence.""" diff --git a/src/kernel/types/browsers/computer_scroll_params.py b/src/kernel/types/browsers/computer_scroll_params.py new file mode 100644 index 00000000..110cb302 --- /dev/null +++ b/src/kernel/types/browsers/computer_scroll_params.py @@ -0,0 +1,26 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from ..._types import SequenceNotStr + +__all__ = ["ComputerScrollParams"] + + +class ComputerScrollParams(TypedDict, total=False): + x: Required[int] + """X coordinate at which to perform the scroll""" + + y: Required[int] + """Y coordinate at which to perform the scroll""" + + delta_x: int + """Horizontal scroll amount. Positive scrolls right, negative scrolls left.""" + + delta_y: int + """Vertical scroll amount. Positive scrolls down, negative scrolls up.""" + + hold_keys: SequenceNotStr[str] + """Modifier keys to hold during the scroll""" diff --git a/src/kernel/types/browsers/computer_type_text_params.py b/src/kernel/types/browsers/computer_type_text_params.py new file mode 100644 index 00000000..3a2c5133 --- /dev/null +++ b/src/kernel/types/browsers/computer_type_text_params.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ComputerTypeTextParams"] + + +class ComputerTypeTextParams(TypedDict, total=False): + text: Required[str] + """Text to type on the browser instance""" + + delay: int + """Delay in milliseconds between keystrokes""" diff --git a/src/kernel/types/extension_create_params.py b/src/kernel/types/extension_upload_params.py similarity index 81% rename from src/kernel/types/extension_create_params.py rename to src/kernel/types/extension_upload_params.py index 6bb2b397..d36dde31 100644 --- a/src/kernel/types/extension_create_params.py +++ b/src/kernel/types/extension_upload_params.py @@ -6,10 +6,10 @@ from .._types import FileTypes -__all__ = ["ExtensionCreateParams"] +__all__ = ["ExtensionUploadParams"] -class ExtensionCreateParams(TypedDict, total=False): +class ExtensionUploadParams(TypedDict, total=False): file: Required[FileTypes] """ZIP file containing the browser extension.""" diff --git a/src/kernel/types/extension_create_response.py b/src/kernel/types/extension_upload_response.py similarity index 88% rename from src/kernel/types/extension_create_response.py rename to src/kernel/types/extension_upload_response.py index c4fd6301..373e8861 100644 --- a/src/kernel/types/extension_create_response.py +++ b/src/kernel/types/extension_upload_response.py @@ -5,10 +5,10 @@ from .._models import BaseModel -__all__ = ["ExtensionCreateResponse"] +__all__ = ["ExtensionUploadResponse"] -class ExtensionCreateResponse(BaseModel): +class ExtensionUploadResponse(BaseModel): id: str """Unique identifier for the extension""" diff --git a/tests/api_resources/browsers/test_computer.py b/tests/api_resources/browsers/test_computer.py new file mode 100644 index 00000000..9e245481 --- /dev/null +++ b/tests/api_resources/browsers/test_computer.py @@ -0,0 +1,892 @@ +# 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 httpx +import pytest +from respx import MockRouter + +from kernel import Kernel, AsyncKernel +from kernel._response import ( + BinaryAPIResponse, + AsyncBinaryAPIResponse, + StreamedBinaryAPIResponse, + AsyncStreamedBinaryAPIResponse, +) + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestComputer: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_capture_screenshot(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + computer = client.browsers.computer.capture_screenshot( + id="id", + ) + assert computer.is_closed + assert computer.json() == {"foo": "bar"} + assert cast(Any, computer.is_closed) is True + assert isinstance(computer, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_capture_screenshot_with_all_params(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + computer = client.browsers.computer.capture_screenshot( + id="id", + region={ + "height": 0, + "width": 0, + "x": 0, + "y": 0, + }, + ) + assert computer.is_closed + assert computer.json() == {"foo": "bar"} + assert cast(Any, computer.is_closed) is True + assert isinstance(computer, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_capture_screenshot(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + computer = client.browsers.computer.with_raw_response.capture_screenshot( + id="id", + ) + + assert computer.is_closed is True + assert computer.http_request.headers.get("X-Stainless-Lang") == "python" + assert computer.json() == {"foo": "bar"} + assert isinstance(computer, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_capture_screenshot(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.browsers.computer.with_streaming_response.capture_screenshot( + id="id", + ) as computer: + assert not computer.is_closed + assert computer.http_request.headers.get("X-Stainless-Lang") == "python" + + assert computer.json() == {"foo": "bar"} + assert cast(Any, computer.is_closed) is True + assert isinstance(computer, StreamedBinaryAPIResponse) + + assert cast(Any, computer.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_capture_screenshot(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.capture_screenshot( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_click_mouse(self, client: Kernel) -> None: + computer = client.browsers.computer.click_mouse( + id="id", + x=0, + y=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_click_mouse_with_all_params(self, client: Kernel) -> None: + computer = client.browsers.computer.click_mouse( + id="id", + x=0, + y=0, + button="left", + click_type="down", + hold_keys=["string"], + num_clicks=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_click_mouse(self, client: Kernel) -> None: + response = client.browsers.computer.with_raw_response.click_mouse( + id="id", + x=0, + y=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_click_mouse(self, client: Kernel) -> None: + with client.browsers.computer.with_streaming_response.click_mouse( + id="id", + x=0, + y=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_click_mouse(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.click_mouse( + id="", + x=0, + y=0, + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_drag_mouse(self, client: Kernel) -> None: + computer = client.browsers.computer.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_drag_mouse_with_all_params(self, client: Kernel) -> None: + computer = client.browsers.computer.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + button="left", + delay=0, + hold_keys=["string"], + step_delay_ms=0, + steps_per_segment=1, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_drag_mouse(self, client: Kernel) -> None: + response = client.browsers.computer.with_raw_response.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_drag_mouse(self, client: Kernel) -> None: + with client.browsers.computer.with_streaming_response.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_drag_mouse(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.drag_mouse( + id="", + path=[[0, 0], [0, 0]], + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_move_mouse(self, client: Kernel) -> None: + computer = client.browsers.computer.move_mouse( + id="id", + x=0, + y=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_move_mouse_with_all_params(self, client: Kernel) -> None: + computer = client.browsers.computer.move_mouse( + id="id", + x=0, + y=0, + hold_keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_move_mouse(self, client: Kernel) -> None: + response = client.browsers.computer.with_raw_response.move_mouse( + id="id", + x=0, + y=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_move_mouse(self, client: Kernel) -> None: + with client.browsers.computer.with_streaming_response.move_mouse( + id="id", + x=0, + y=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_move_mouse(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.move_mouse( + id="", + x=0, + y=0, + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_press_key(self, client: Kernel) -> None: + computer = client.browsers.computer.press_key( + id="id", + keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_press_key_with_all_params(self, client: Kernel) -> None: + computer = client.browsers.computer.press_key( + id="id", + keys=["string"], + duration=0, + hold_keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_press_key(self, client: Kernel) -> None: + response = client.browsers.computer.with_raw_response.press_key( + id="id", + keys=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_press_key(self, client: Kernel) -> None: + with client.browsers.computer.with_streaming_response.press_key( + id="id", + keys=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_press_key(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.press_key( + id="", + keys=["string"], + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_scroll(self, client: Kernel) -> None: + computer = client.browsers.computer.scroll( + id="id", + x=0, + y=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_scroll_with_all_params(self, client: Kernel) -> None: + computer = client.browsers.computer.scroll( + id="id", + x=0, + y=0, + delta_x=0, + delta_y=0, + hold_keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_scroll(self, client: Kernel) -> None: + response = client.browsers.computer.with_raw_response.scroll( + id="id", + x=0, + y=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_scroll(self, client: Kernel) -> None: + with client.browsers.computer.with_streaming_response.scroll( + id="id", + x=0, + y=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_scroll(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.scroll( + id="", + x=0, + y=0, + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_type_text(self, client: Kernel) -> None: + computer = client.browsers.computer.type_text( + id="id", + text="text", + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_method_type_text_with_all_params(self, client: Kernel) -> None: + computer = client.browsers.computer.type_text( + id="id", + text="text", + delay=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_raw_response_type_text(self, client: Kernel) -> None: + response = client.browsers.computer.with_raw_response.type_text( + id="id", + text="text", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_streaming_response_type_text(self, client: Kernel) -> None: + with client.browsers.computer.with_streaming_response.type_text( + id="id", + text="text", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + def test_path_params_type_text(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.browsers.computer.with_raw_response.type_text( + id="", + text="text", + ) + + +class TestAsyncComputer: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_capture_screenshot(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + computer = await async_client.browsers.computer.capture_screenshot( + id="id", + ) + assert computer.is_closed + assert await computer.json() == {"foo": "bar"} + assert cast(Any, computer.is_closed) is True + assert isinstance(computer, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_capture_screenshot_with_all_params( + self, async_client: AsyncKernel, respx_mock: MockRouter + ) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + computer = await async_client.browsers.computer.capture_screenshot( + id="id", + region={ + "height": 0, + "width": 0, + "x": 0, + "y": 0, + }, + ) + assert computer.is_closed + assert await computer.json() == {"foo": "bar"} + assert cast(Any, computer.is_closed) is True + assert isinstance(computer, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_capture_screenshot(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + computer = await async_client.browsers.computer.with_raw_response.capture_screenshot( + id="id", + ) + + assert computer.is_closed is True + assert computer.http_request.headers.get("X-Stainless-Lang") == "python" + assert await computer.json() == {"foo": "bar"} + assert isinstance(computer, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_capture_screenshot( + self, async_client: AsyncKernel, respx_mock: MockRouter + ) -> None: + respx_mock.post("/browsers/id/computer/screenshot").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.browsers.computer.with_streaming_response.capture_screenshot( + id="id", + ) as computer: + assert not computer.is_closed + assert computer.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await computer.json() == {"foo": "bar"} + assert cast(Any, computer.is_closed) is True + assert isinstance(computer, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, computer.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_capture_screenshot(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.capture_screenshot( + id="", + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_click_mouse(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.click_mouse( + id="id", + x=0, + y=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_click_mouse_with_all_params(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.click_mouse( + id="id", + x=0, + y=0, + button="left", + click_type="down", + hold_keys=["string"], + num_clicks=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_click_mouse(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.computer.with_raw_response.click_mouse( + id="id", + x=0, + y=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_click_mouse(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.computer.with_streaming_response.click_mouse( + id="id", + x=0, + y=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_click_mouse(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.click_mouse( + id="", + x=0, + y=0, + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_drag_mouse(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_drag_mouse_with_all_params(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + button="left", + delay=0, + hold_keys=["string"], + step_delay_ms=0, + steps_per_segment=1, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_drag_mouse(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.computer.with_raw_response.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_drag_mouse(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.computer.with_streaming_response.drag_mouse( + id="id", + path=[[0, 0], [0, 0]], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_drag_mouse(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.drag_mouse( + id="", + path=[[0, 0], [0, 0]], + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_move_mouse(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.move_mouse( + id="id", + x=0, + y=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_move_mouse_with_all_params(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.move_mouse( + id="id", + x=0, + y=0, + hold_keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_move_mouse(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.computer.with_raw_response.move_mouse( + id="id", + x=0, + y=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_move_mouse(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.computer.with_streaming_response.move_mouse( + id="id", + x=0, + y=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_move_mouse(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.move_mouse( + id="", + x=0, + y=0, + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_press_key(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.press_key( + id="id", + keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_press_key_with_all_params(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.press_key( + id="id", + keys=["string"], + duration=0, + hold_keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_press_key(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.computer.with_raw_response.press_key( + id="id", + keys=["string"], + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_press_key(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.computer.with_streaming_response.press_key( + id="id", + keys=["string"], + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_press_key(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.press_key( + id="", + keys=["string"], + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_scroll(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.scroll( + id="id", + x=0, + y=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_scroll_with_all_params(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.scroll( + id="id", + x=0, + y=0, + delta_x=0, + delta_y=0, + hold_keys=["string"], + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_scroll(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.computer.with_raw_response.scroll( + id="id", + x=0, + y=0, + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_scroll(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.computer.with_streaming_response.scroll( + id="id", + x=0, + y=0, + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_scroll(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.scroll( + id="", + x=0, + y=0, + ) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_type_text(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.type_text( + id="id", + text="text", + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_type_text_with_all_params(self, async_client: AsyncKernel) -> None: + computer = await async_client.browsers.computer.type_text( + id="id", + text="text", + delay=0, + ) + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_type_text(self, async_client: AsyncKernel) -> None: + response = await async_client.browsers.computer.with_raw_response.type_text( + id="id", + text="text", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + computer = await response.parse() + assert computer is None + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_type_text(self, async_client: AsyncKernel) -> None: + async with async_client.browsers.computer.with_streaming_response.type_text( + id="id", + text="text", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + computer = await response.parse() + assert computer is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_path_params_type_text(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.browsers.computer.with_raw_response.type_text( + id="", + text="text", + ) diff --git a/tests/api_resources/test_extensions.py b/tests/api_resources/test_extensions.py index ffdb02f6..5d61f327 100644 --- a/tests/api_resources/test_extensions.py +++ b/tests/api_resources/test_extensions.py @@ -13,7 +13,7 @@ from tests.utils import assert_matches_type from kernel.types import ( ExtensionListResponse, - ExtensionCreateResponse, + ExtensionUploadResponse, ) from kernel._response import ( BinaryAPIResponse, @@ -28,99 +28,6 @@ class TestExtensions: 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: Kernel) -> None: - extension = client.extensions.create( - file=b"raw file contents", - ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_method_create_with_all_params(self, client: Kernel) -> None: - extension = client.extensions.create( - file=b"raw file contents", - name="name", - ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_raw_response_create(self, client: Kernel) -> None: - response = client.extensions.with_raw_response.create( - file=b"raw file contents", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - extension = response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) - - @pytest.mark.skip(reason="Prism tests are disabled") - @parametrize - def test_streaming_response_create(self, client: Kernel) -> None: - with client.extensions.with_streaming_response.create( - file=b"raw file contents", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - extension = response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_method_retrieve(self, client: Kernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - extension = client.extensions.retrieve( - "id_or_name", - ) - assert extension.is_closed - assert extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_raw_response_retrieve(self, client: Kernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - extension = client.extensions.with_raw_response.retrieve( - "id_or_name", - ) - - assert extension.is_closed is True - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - assert extension.json() == {"foo": "bar"} - assert isinstance(extension, BinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_streaming_response_retrieve(self, client: Kernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - with client.extensions.with_streaming_response.retrieve( - "id_or_name", - ) as extension: - assert not extension.is_closed - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - - assert extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, StreamedBinaryAPIResponse) - - assert cast(Any, extension.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - def test_path_params_retrieve(self, client: Kernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): - client.extensions.with_raw_response.retrieve( - "", - ) - @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list(self, client: Kernel) -> None: @@ -191,6 +98,56 @@ def test_path_params_delete(self, client: Kernel) -> None: "", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_method_download(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + extension = client.extensions.download( + "id_or_name", + ) + assert extension.is_closed + assert extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_raw_response_download(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + extension = client.extensions.with_raw_response.download( + "id_or_name", + ) + + assert extension.is_closed is True + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + assert extension.json() == {"foo": "bar"} + assert isinstance(extension, BinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_streaming_response_download(self, client: Kernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + with client.extensions.with_streaming_response.download( + "id_or_name", + ) as extension: + assert not extension.is_closed + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + + assert extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, StreamedBinaryAPIResponse) + + assert cast(Any, extension.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + def test_path_params_download(self, client: Kernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): + client.extensions.with_raw_response.download( + "", + ) + @parametrize @pytest.mark.respx(base_url=base_url) def test_method_download_from_chrome_store(self, client: Kernel, respx_mock: MockRouter) -> None: @@ -246,104 +203,54 @@ def test_streaming_response_download_from_chrome_store(self, client: Kernel, res assert cast(Any, extension.is_closed) is True - -class TestAsyncExtensions: - 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: AsyncKernel) -> None: - extension = await async_client.extensions.create( + def test_method_upload(self, client: Kernel) -> None: + extension = client.extensions.upload( file=b"raw file contents", ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncKernel) -> None: - extension = await async_client.extensions.create( + def test_method_upload_with_all_params(self, client: Kernel) -> None: + extension = client.extensions.upload( file=b"raw file contents", name="name", ) - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_raw_response_create(self, async_client: AsyncKernel) -> None: - response = await async_client.extensions.with_raw_response.create( + def test_raw_response_upload(self, client: Kernel) -> None: + response = client.extensions.with_raw_response.upload( file=b"raw file contents", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - extension = await response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + extension = response.parse() + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize - async def test_streaming_response_create(self, async_client: AsyncKernel) -> None: - async with async_client.extensions.with_streaming_response.create( + def test_streaming_response_upload(self, client: Kernel) -> None: + with client.extensions.with_streaming_response.upload( file=b"raw file contents", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - extension = await response.parse() - assert_matches_type(ExtensionCreateResponse, extension, path=["response"]) + extension = response.parse() + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) assert cast(Any, response.is_closed) is True - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_method_retrieve(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - extension = await async_client.extensions.retrieve( - "id_or_name", - ) - assert extension.is_closed - assert await extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, AsyncBinaryAPIResponse) - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_raw_response_retrieve(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - - extension = await async_client.extensions.with_raw_response.retrieve( - "id_or_name", - ) - - assert extension.is_closed is True - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - assert await extension.json() == {"foo": "bar"} - assert isinstance(extension, AsyncBinaryAPIResponse) - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_streaming_response_retrieve(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: - respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) - async with async_client.extensions.with_streaming_response.retrieve( - "id_or_name", - ) as extension: - assert not extension.is_closed - assert extension.http_request.headers.get("X-Stainless-Lang") == "python" - - assert await extension.json() == {"foo": "bar"} - assert cast(Any, extension.is_closed) is True - assert isinstance(extension, AsyncStreamedBinaryAPIResponse) - - assert cast(Any, extension.is_closed) is True - - @parametrize - @pytest.mark.respx(base_url=base_url) - async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): - await async_client.extensions.with_raw_response.retrieve( - "", - ) +class TestAsyncExtensions: + 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 @@ -415,6 +322,56 @@ async def test_path_params_delete(self, async_client: AsyncKernel) -> None: "", ) + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_method_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + extension = await async_client.extensions.download( + "id_or_name", + ) + assert extension.is_closed + assert await extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_raw_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + + extension = await async_client.extensions.with_raw_response.download( + "id_or_name", + ) + + assert extension.is_closed is True + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + assert await extension.json() == {"foo": "bar"} + assert isinstance(extension, AsyncBinaryAPIResponse) + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_streaming_response_download(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: + respx_mock.get("/extensions/id_or_name").mock(return_value=httpx.Response(200, json={"foo": "bar"})) + async with async_client.extensions.with_streaming_response.download( + "id_or_name", + ) as extension: + assert not extension.is_closed + assert extension.http_request.headers.get("X-Stainless-Lang") == "python" + + assert await extension.json() == {"foo": "bar"} + assert cast(Any, extension.is_closed) is True + assert isinstance(extension, AsyncStreamedBinaryAPIResponse) + + assert cast(Any, extension.is_closed) is True + + @parametrize + @pytest.mark.respx(base_url=base_url) + async def test_path_params_download(self, async_client: AsyncKernel) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id_or_name` but received ''"): + await async_client.extensions.with_raw_response.download( + "", + ) + @parametrize @pytest.mark.respx(base_url=base_url) async def test_method_download_from_chrome_store(self, async_client: AsyncKernel, respx_mock: MockRouter) -> None: @@ -475,3 +432,46 @@ async def test_streaming_response_download_from_chrome_store( assert isinstance(extension, AsyncStreamedBinaryAPIResponse) assert cast(Any, extension.is_closed) is True + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_upload(self, async_client: AsyncKernel) -> None: + extension = await async_client.extensions.upload( + file=b"raw file contents", + ) + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_method_upload_with_all_params(self, async_client: AsyncKernel) -> None: + extension = await async_client.extensions.upload( + file=b"raw file contents", + name="name", + ) + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_raw_response_upload(self, async_client: AsyncKernel) -> None: + response = await async_client.extensions.with_raw_response.upload( + file=b"raw file contents", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + extension = await response.parse() + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + + @pytest.mark.skip(reason="Prism tests are disabled") + @parametrize + async def test_streaming_response_upload(self, async_client: AsyncKernel) -> None: + async with async_client.extensions.with_streaming_response.upload( + file=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + extension = await response.parse() + assert_matches_type(ExtensionUploadResponse, extension, path=["response"]) + + assert cast(Any, response.is_closed) is True From 300c640a8339d84965f0719438b972b9aa55ec61 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 17 Oct 2025 18:33:46 +0000 Subject: [PATCH 3/3] release: 0.15.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- src/kernel/_version.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3d26904f..8f3e0a49 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.14.2" + ".": "0.15.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e1ed7b..e9fc2228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.15.0 (2025-10-17) + +Full Changelog: [v0.14.2...v0.15.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.14.2...v0.15.0) + +### Features + +* click mouse, move mouse, screenshot ([b63f7ad](https://github.com/onkernel/kernel-python-sdk/commit/b63f7adc7cf0da614a4fc3a9eed491f71f7ec742)) +* Phani/deploy with GitHub url ([eb86c27](https://github.com/onkernel/kernel-python-sdk/commit/eb86c27107781172bf740e0af3833eee86b7cb60)) + ## 0.14.2 (2025-10-16) Full Changelog: [v0.14.1...v0.14.2](https://github.com/onkernel/kernel-python-sdk/compare/v0.14.1...v0.14.2) diff --git a/pyproject.toml b/pyproject.toml index c2e14c5d..0f8cb140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.14.2" +version = "0.15.0" description = "The official Python library for the kernel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/kernel/_version.py b/src/kernel/_version.py index dfcce591..e65ca7f2 100644 --- a/src/kernel/_version.py +++ b/src/kernel/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "kernel" -__version__ = "0.14.2" # x-release-please-version +__version__ = "0.15.0" # x-release-please-version