diff --git a/README.md b/README.md index cfec542..109ec92 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,10 @@ client = Riza( api_key=os.environ.get("RIZA_API_KEY"), ) -sandbox_execute_response = client.sandbox.execute() +sandbox_execute_response = client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", +) print(sandbox_execute_response.exit_code) ``` @@ -57,7 +60,10 @@ client = AsyncRiza( async def main() -> None: - sandbox_execute_response = await client.sandbox.execute() + sandbox_execute_response = await client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", + ) print(sandbox_execute_response.exit_code) @@ -91,7 +97,10 @@ from rizaio import Riza client = Riza() try: - client.sandbox.execute() + client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", + ) except rizaio.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -134,7 +143,10 @@ client = Riza( ) # Or, configure per-request: -client.with_options(max_retries=5).sandbox.execute() +client.with_options(max_retries=5).sandbox.execute( + code='print("Hello world!")', + language="PYTHON", +) ``` ### Timeouts @@ -157,7 +169,10 @@ client = Riza( ) # Override per-request: -client.with_options(timeout=5 * 1000).sandbox.execute() +client.with_options(timeout=5 * 1000).sandbox.execute( + code='print("Hello world!")', + language="PYTHON", +) ``` On timeout, an `APITimeoutError` is thrown. @@ -196,7 +211,10 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from rizaio import Riza client = Riza() -response = client.sandbox.with_raw_response.execute() +response = client.sandbox.with_raw_response.execute( + code="print(\"Hello world!\")", + language="PYTHON", +) print(response.headers.get('X-My-Header')) sandbox = response.parse() # get the object that `sandbox.execute()` would have returned @@ -214,7 +232,10 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.sandbox.with_streaming_response.execute() as response: +with client.sandbox.with_streaming_response.execute( + code='print("Hello world!")', + language="PYTHON", +) as response: print(response.headers.get("X-My-Header")) for line in response.iter_lines(): diff --git a/src/rizaio/resources/sandbox.py b/src/rizaio/resources/sandbox.py index bc3a32a..9245785 100644 --- a/src/rizaio/resources/sandbox.py +++ b/src/rizaio/resources/sandbox.py @@ -40,10 +40,10 @@ def with_streaming_response(self) -> SandboxWithStreamingResponse: def execute( self, *, + code: str, + language: Literal["PYTHON", "JAVASCRIPT", "TYPESCRIPT", "RUBY", "PHP"], args: List[str] | NotGiven = NOT_GIVEN, - code: str | NotGiven = NOT_GIVEN, env: Dict[str, str] | NotGiven = NOT_GIVEN, - language: Literal["UNSPECIFIED", "PYTHON", "JAVASCRIPT", "TYPESCRIPT", "RUBY", "PHP"] | NotGiven = NOT_GIVEN, stdin: str | NotGiven = NOT_GIVEN, # 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. @@ -52,8 +52,23 @@ def execute( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SandboxExecuteResponse: - """ + """Run a script in a secure, isolated sandbox. + + Scripts can read from stdin and + write to stdout or stderr. They can access environment variables and command + line arguments. + Args: + code: The code to execute in the sandbox. + + language: The interpreter to use when executing code. + + args: List of command line arguments to pass to the script. + + env: Set of key-value pairs to add to the script's execution environment. + + stdin: Input to pass to the script via `stdin`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -66,10 +81,10 @@ def execute( "/v1/execute", body=maybe_transform( { - "args": args, "code": code, - "env": env, "language": language, + "args": args, + "env": env, "stdin": stdin, }, sandbox_execute_params.SandboxExecuteParams, @@ -93,10 +108,10 @@ def with_streaming_response(self) -> AsyncSandboxWithStreamingResponse: async def execute( self, *, + code: str, + language: Literal["PYTHON", "JAVASCRIPT", "TYPESCRIPT", "RUBY", "PHP"], args: List[str] | NotGiven = NOT_GIVEN, - code: str | NotGiven = NOT_GIVEN, env: Dict[str, str] | NotGiven = NOT_GIVEN, - language: Literal["UNSPECIFIED", "PYTHON", "JAVASCRIPT", "TYPESCRIPT", "RUBY", "PHP"] | NotGiven = NOT_GIVEN, stdin: str | NotGiven = NOT_GIVEN, # 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. @@ -105,8 +120,23 @@ async def execute( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SandboxExecuteResponse: - """ + """Run a script in a secure, isolated sandbox. + + Scripts can read from stdin and + write to stdout or stderr. They can access environment variables and command + line arguments. + Args: + code: The code to execute in the sandbox. + + language: The interpreter to use when executing code. + + args: List of command line arguments to pass to the script. + + env: Set of key-value pairs to add to the script's execution environment. + + stdin: Input to pass to the script via `stdin`. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -119,10 +149,10 @@ async def execute( "/v1/execute", body=await async_maybe_transform( { - "args": args, "code": code, - "env": env, "language": language, + "args": args, + "env": env, "stdin": stdin, }, sandbox_execute_params.SandboxExecuteParams, diff --git a/src/rizaio/types/sandbox_execute_params.py b/src/rizaio/types/sandbox_execute_params.py index 13cc482..5813744 100644 --- a/src/rizaio/types/sandbox_execute_params.py +++ b/src/rizaio/types/sandbox_execute_params.py @@ -3,18 +3,23 @@ from __future__ import annotations from typing import Dict, List -from typing_extensions import Literal, TypedDict +from typing_extensions import Literal, Required, TypedDict __all__ = ["SandboxExecuteParams"] class SandboxExecuteParams(TypedDict, total=False): - args: List[str] + code: Required[str] + """The code to execute in the sandbox.""" - code: str + language: Required[Literal["PYTHON", "JAVASCRIPT", "TYPESCRIPT", "RUBY", "PHP"]] + """The interpreter to use when executing code.""" - env: Dict[str, str] + args: List[str] + """List of command line arguments to pass to the script.""" - language: Literal["UNSPECIFIED", "PYTHON", "JAVASCRIPT", "TYPESCRIPT", "RUBY", "PHP"] + env: Dict[str, str] + """Set of key-value pairs to add to the script's execution environment.""" stdin: str + """Input to pass to the script via `stdin`.""" diff --git a/src/rizaio/types/sandbox_execute_response.py b/src/rizaio/types/sandbox_execute_response.py index b9af54e..4439f81 100644 --- a/src/rizaio/types/sandbox_execute_response.py +++ b/src/rizaio/types/sandbox_execute_response.py @@ -9,7 +9,13 @@ class SandboxExecuteResponse(BaseModel): exit_code: Optional[int] = None + """The exit code returned by the script. + + Will be `0` on success and non-zero on failure. + """ stderr: Optional[str] = None + """The contents of `stderr` after executing the script.""" stdout: Optional[str] = None + """The contents of `stdout` after executing the script.""" diff --git a/tests/api_resources/test_sandbox.py b/tests/api_resources/test_sandbox.py index 646ecf0..fe139ff 100644 --- a/tests/api_resources/test_sandbox.py +++ b/tests/api_resources/test_sandbox.py @@ -19,23 +19,29 @@ class TestSandbox: @parametrize def test_method_execute(self, client: Riza) -> None: - sandbox = client.sandbox.execute() + sandbox = client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", + ) assert_matches_type(SandboxExecuteResponse, sandbox, path=["response"]) @parametrize def test_method_execute_with_all_params(self, client: Riza) -> None: sandbox = client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", args=["string", "string", "string"], - code="string", env={"foo": "string"}, - language="UNSPECIFIED", stdin="string", ) assert_matches_type(SandboxExecuteResponse, sandbox, path=["response"]) @parametrize def test_raw_response_execute(self, client: Riza) -> None: - response = client.sandbox.with_raw_response.execute() + response = client.sandbox.with_raw_response.execute( + code='print("Hello world!")', + language="PYTHON", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -44,7 +50,10 @@ def test_raw_response_execute(self, client: Riza) -> None: @parametrize def test_streaming_response_execute(self, client: Riza) -> None: - with client.sandbox.with_streaming_response.execute() as response: + with client.sandbox.with_streaming_response.execute( + code='print("Hello world!")', + language="PYTHON", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -59,23 +68,29 @@ class TestAsyncSandbox: @parametrize async def test_method_execute(self, async_client: AsyncRiza) -> None: - sandbox = await async_client.sandbox.execute() + sandbox = await async_client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", + ) assert_matches_type(SandboxExecuteResponse, sandbox, path=["response"]) @parametrize async def test_method_execute_with_all_params(self, async_client: AsyncRiza) -> None: sandbox = await async_client.sandbox.execute( + code='print("Hello world!")', + language="PYTHON", args=["string", "string", "string"], - code="string", env={"foo": "string"}, - language="UNSPECIFIED", stdin="string", ) assert_matches_type(SandboxExecuteResponse, sandbox, path=["response"]) @parametrize async def test_raw_response_execute(self, async_client: AsyncRiza) -> None: - response = await async_client.sandbox.with_raw_response.execute() + response = await async_client.sandbox.with_raw_response.execute( + code='print("Hello world!")', + language="PYTHON", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -84,7 +99,10 @@ async def test_raw_response_execute(self, async_client: AsyncRiza) -> None: @parametrize async def test_streaming_response_execute(self, async_client: AsyncRiza) -> None: - async with async_client.sandbox.with_streaming_response.execute() as response: + async with async_client.sandbox.with_streaming_response.execute( + code='print("Hello world!")', + language="PYTHON", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/test_client.py b/tests/test_client.py index ef53f35..a8b33c4 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -697,7 +697,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No with pytest.raises(APITimeoutError): self.client.post( "/v1/execute", - body=cast(object, dict()), + body=cast(object, dict(code='print("Hello world!")', language="PYTHON")), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -712,7 +712,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non with pytest.raises(APIStatusError): self.client.post( "/v1/execute", - body=cast(object, dict()), + body=cast(object, dict(code='print("Hello world!")', language="PYTHON")), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -1384,7 +1384,7 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) with pytest.raises(APITimeoutError): await self.client.post( "/v1/execute", - body=cast(object, dict()), + body=cast(object, dict(code='print("Hello world!")', language="PYTHON")), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) @@ -1399,7 +1399,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) with pytest.raises(APIStatusError): await self.client.post( "/v1/execute", - body=cast(object, dict()), + body=cast(object, dict(code='print("Hello world!")', language="PYTHON")), cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, )