From df06fdf1fd3534a47862a98e9df3868dc8813dd9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 03:37:06 +0000 Subject: [PATCH 01/27] chore(internal): avoid errors for isinstance checks on proxies --- src/rizaio/_utils/_proxy.py | 5 ++++- tests/test_utils/test_proxy.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/rizaio/_utils/_proxy.py b/src/rizaio/_utils/_proxy.py index ffd883e9..0f239a33 100644 --- a/src/rizaio/_utils/_proxy.py +++ b/src/rizaio/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index 98ee66bf..355257b4 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) From 5a8f2bd588ed788643b59c1f64bf88af21f8774e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 10 May 2025 03:13:38 +0000 Subject: [PATCH 02/27] fix(package): support direct resource imports --- src/rizaio/__init__.py | 5 +++++ src/rizaio/_utils/_resources_proxy.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/rizaio/_utils/_resources_proxy.py diff --git a/src/rizaio/__init__.py b/src/rizaio/__init__.py index d5f0908f..98970cff 100644 --- a/src/rizaio/__init__.py +++ b/src/rizaio/__init__.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path @@ -68,6 +70,9 @@ "DefaultAsyncHttpxClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/rizaio/_utils/_resources_proxy.py b/src/rizaio/_utils/_resources_proxy.py new file mode 100644 index 00000000..7d704ea4 --- /dev/null +++ b/src/rizaio/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `rizaio.resources` module. + + This is used so that we can lazily import `rizaio.resources` only when + needed *and* so that users can just import `rizaio` and reference `rizaio.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("rizaio.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() From 6f15584c25e495105f80fe0728b693335a431355 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 04:17:03 +0000 Subject: [PATCH 03/27] chore(ci): upload sdks to package manager --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ scripts/utils/upload-artifact.sh | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 scripts/utils/upload-artifact.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf004512..4beb1c07 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,30 @@ jobs: - name: Run lints run: ./scripts/lint + upload: + if: github.repository == 'stainless-sdks/riza-api-python' + timeout-minutes: 10 + name: upload + permissions: + contents: read + id-token: write + runs-on: depot-ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Get GitHub OIDC Token + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + test: timeout-minutes: 10 name: test diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..26fa87a9 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary @- "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/riza-api-python/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi From dbfcc815e5022d343677c8eae511024949838c37 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 03:25:29 +0000 Subject: [PATCH 04/27] chore(ci): fix installation instructions --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 26fa87a9..ee0ba42e 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/riza-api-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/riza-api-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 8254ef91bec882adfb05e4d86dfc13c0b0abcaca Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 02:34:40 +0000 Subject: [PATCH 05/27] chore(docs): grammar improvements --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index e000ce1b..1bf52af9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Riza please follow the respective company's security reporting guidelines. +or products provided by Riza, please follow the respective company's security reporting guidelines. ### Riza Terms and Policies -Please contact hello@riza.io for any questions or concerns regarding security of our services. +Please contact hello@riza.io for any questions or concerns regarding the security of our services. --- From e7c147beb4fc7f74c0e883389c53e2490863a8e2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:26:39 +0000 Subject: [PATCH 06/27] chore(docs): remove reference to rye shell --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bcd5aa8a..07fcc43d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix From 5dc8d89c62b934da26ef8a588a26452e64aec895 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:38:46 +0000 Subject: [PATCH 07/27] chore(docs): remove unnecessary param examples --- README.md | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/README.md b/README.md index ff1d6751..461dc13e 100644 --- a/README.md +++ b/README.md @@ -91,28 +91,7 @@ client = Riza() response = client.command.exec( code='print("Hello world!")', language="python", - http={ - "allow": [ - { - "auth": { - "basic": { - "password": "password", - "user_id": "user_id", - }, - "bearer": {"token": "token"}, - "header": { - "name": "name", - "value": "value", - }, - "query": { - "key": "key", - "value": "value", - }, - }, - "host": "host", - } - ] - }, + http={}, ) print(response.http) ``` From 729c66e844308a7a56c4a7c56ce5af42f4abed0d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 03:42:10 +0000 Subject: [PATCH 08/27] feat(client): add follow_redirects request option --- src/rizaio/_base_client.py | 6 +++++ src/rizaio/_models.py | 2 ++ src/rizaio/_types.py | 2 ++ tests/test_client.py | 54 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/src/rizaio/_base_client.py b/src/rizaio/_base_client.py index 72686805..ed9de132 100644 --- a/src/rizaio/_base_client.py +++ b/src/rizaio/_base_client.py @@ -960,6 +960,9 @@ def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1460,6 +1463,9 @@ async def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None diff --git a/src/rizaio/_models.py b/src/rizaio/_models.py index b3c35b65..4b399147 100644 --- a/src/rizaio/_models.py +++ b/src/rizaio/_models.py @@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): idempotency_key: str json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. diff --git a/src/rizaio/_types.py b/src/rizaio/_types.py index 544cbd65..cfa3307c 100644 --- a/src/rizaio/_types.py +++ b/src/rizaio/_types.py @@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted @@ -215,3 +216,4 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool diff --git a/tests/test_client.py b/tests/test_client.py index 8872838b..a1c70a3d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -811,6 +811,33 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + class TestAsyncRiza: client = AsyncRiza(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -1632,3 +1659,30 @@ async def test_main() -> None: raise AssertionError("calling get_platform using asyncify resulted in a hung process") time.sleep(0.1) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" From 333d1682a5d6f11000efba046a7874a156a6b4da Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:09:12 +0000 Subject: [PATCH 09/27] feat(api): api update --- .stats.yml | 4 ++-- README.md | 6 +++--- src/rizaio/types/command_exec_func_response.py | 3 +++ src/rizaio/types/command_exec_response.py | 3 +++ src/rizaio/types/tool_exec_response.py | 3 +++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 9c0ef9df..f896a730 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-f9777846c474c861e6c31174f8f08fd46a68a896609c8841b924d4ba4cb6979b.yml -openapi_spec_hash: 65364a3f3566c609aaed0b5c69600540 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-c6f1dde474ec32415d38662194da07fbafb6b67b7e12f2f87c2dc1f9475072c6.yml +openapi_spec_hash: 6b0fae240b983793c40061912271fa1a config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 diff --git a/README.md b/README.md index 461dc13e..b14f0be0 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ response = client.command.exec( code="print('Hello, World!')", language="python", ) -print(response.duration) +print(response.id) ``` While you can provide an `api_key` keyword argument, @@ -62,7 +62,7 @@ async def main() -> None: code="print('Hello, World!')", language="python", ) - print(response.duration) + print(response.id) asyncio.run(main()) @@ -235,7 +235,7 @@ response = client.command.with_raw_response.exec( print(response.headers.get('X-My-Header')) command = response.parse() # get the object that `command.exec()` would have returned -print(command.duration) +print(command.id) ``` These methods return an [`APIResponse`](https://github.com/riza-io/riza-api-python/tree/main/src/rizaio/_response.py) object. diff --git a/src/rizaio/types/command_exec_func_response.py b/src/rizaio/types/command_exec_func_response.py index 59c3365a..8920bf87 100644 --- a/src/rizaio/types/command_exec_func_response.py +++ b/src/rizaio/types/command_exec_func_response.py @@ -8,6 +8,9 @@ class Execution(BaseModel): + id: str + """The ID of the execution.""" + duration: int """The execution time of the function in milliseconds.""" diff --git a/src/rizaio/types/command_exec_response.py b/src/rizaio/types/command_exec_response.py index 5b6dd41a..848e1f26 100644 --- a/src/rizaio/types/command_exec_response.py +++ b/src/rizaio/types/command_exec_response.py @@ -6,6 +6,9 @@ class CommandExecResponse(BaseModel): + id: str + """The ID of the execution.""" + duration: int """The execution time of the script in milliseconds.""" diff --git a/src/rizaio/types/tool_exec_response.py b/src/rizaio/types/tool_exec_response.py index 0656ceda..e4605835 100644 --- a/src/rizaio/types/tool_exec_response.py +++ b/src/rizaio/types/tool_exec_response.py @@ -8,6 +8,9 @@ class Execution(BaseModel): + id: str + """The ID of the execution.""" + duration: int """The execution time of the function in milliseconds.""" From 9c06af9e919b3037638e51813d6172d6f5e9318e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:12:58 +0000 Subject: [PATCH 10/27] chore(tests): run tests in parallel --- pyproject.toml | 3 ++- requirements-dev.lock | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c635e9ff..a861ab99 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -125,7 +126,7 @@ replacement = '[\1](https://github.com/riza-io/riza-api-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" diff --git a/requirements-dev.lock b/requirements-dev.lock index bbf7b621..9019df21 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -30,6 +30,8 @@ distro==1.8.0 exceptiongroup==1.2.2 # via anyio # via pytest +execnet==2.1.1 + # via pytest-xdist filelock==3.12.4 # via virtualenv h11==0.14.0 @@ -72,7 +74,9 @@ pygments==2.18.0 pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio + # via pytest-xdist pytest-asyncio==0.24.0 +pytest-xdist==3.7.0 python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 From 2b6e46ffc653e27831681d2a6e3719f6f2e79d4d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 13 Jun 2025 02:37:32 +0000 Subject: [PATCH 11/27] fix(client): correctly parse binary response | stream --- src/rizaio/_base_client.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/rizaio/_base_client.py b/src/rizaio/_base_client.py index ed9de132..54d4f617 100644 --- a/src/rizaio/_base_client.py +++ b/src/rizaio/_base_client.py @@ -1071,7 +1071,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1574,7 +1581,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") From cd2c84cb45f6848e72f7264579bb7a1d3c81a9be Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 14 Jun 2025 00:54:31 +0000 Subject: [PATCH 12/27] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index f896a730..28cd909d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-c6f1dde474ec32415d38662194da07fbafb6b67b7e12f2f87c2dc1f9475072c6.yml -openapi_spec_hash: 6b0fae240b983793c40061912271fa1a +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-5162c158ef44f61f37af3cd1dc1ef424d9aa5c75c9c1f1aef1ad2f4178ae3f11.yml +openapi_spec_hash: e8f14024df1b1c3c4fdd14f9865e989b config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 From 2c5c78d175e9e780d23ca32edaacb4514d8c0e4a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:07:39 +0000 Subject: [PATCH 13/27] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 28cd909d..7a7bfc3d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-5162c158ef44f61f37af3cd1dc1ef424d9aa5c75c9c1f1aef1ad2f4178ae3f11.yml -openapi_spec_hash: e8f14024df1b1c3c4fdd14f9865e989b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-4efd11e43040094f8b45765d7f83a146a0d2398fbc14300935240e879e6a4870.yml +openapi_spec_hash: d1319a266702009339eece99402ee25f config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 From 81ec20b4c46eacb48f8f715aa195d621e2e663de Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 02:42:13 +0000 Subject: [PATCH 14/27] chore(tests): add tests for httpx client instantiation & proxies --- tests/test_client.py | 53 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index a1c70a3d..2c880d24 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -27,7 +27,14 @@ from rizaio._models import BaseModel, FinalRequestOptions from rizaio._constants import RAW_RESPONSE_HEADER from rizaio._exceptions import RizaError, APIStatusError, APITimeoutError, APIResponseValidationError -from rizaio._base_client import DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options +from rizaio._base_client import ( + DEFAULT_TIMEOUT, + HTTPX_DEFAULT_TIMEOUT, + BaseClient, + DefaultHttpxClient, + DefaultAsyncHttpxClient, + make_request_options, +) from rizaio.types.command_exec_params import CommandExecParams from .utils import update_env @@ -811,6 +818,28 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + @pytest.mark.respx(base_url=base_url) def test_follow_redirects(self, respx_mock: MockRouter) -> None: # Test that the default follow_redirects=True allows following redirects @@ -1660,6 +1689,28 @@ async def test_main() -> None: time.sleep(0.1) + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + @pytest.mark.respx(base_url=base_url) async def test_follow_redirects(self, respx_mock: MockRouter) -> None: # Test that the default follow_redirects=True allows following redirects From bd8931e52ca3ae4591b692aafeded90dfc1d20fd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 04:12:14 +0000 Subject: [PATCH 15/27] chore(internal): update conftest.py --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 77f7f7cf..962ca6d9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os From ce5b546ced715900bff3f6d5a965ff482c20d46c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 06:42:55 +0000 Subject: [PATCH 16/27] chore(ci): enable for pull requests --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4beb1c07..1f56b9fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: From 90483e2bbd510c1f10335a658dcc0642f3921a2c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 20:37:41 +0000 Subject: [PATCH 17/27] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7a7bfc3d..6c2c5f75 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-4efd11e43040094f8b45765d7f83a146a0d2398fbc14300935240e879e6a4870.yml -openapi_spec_hash: d1319a266702009339eece99402ee25f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-493a8f28a2b8639a700d58e87531dac23a542fd518be55d09933c8622ad2b914.yml +openapi_spec_hash: 2e20722d36e979a3f99c759374900f0f config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 From a05d13a7c3f75db6625eefc73c754015c8bb1c39 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:49:41 +0000 Subject: [PATCH 18/27] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6c2c5f75..05ecd3be 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-493a8f28a2b8639a700d58e87531dac23a542fd518be55d09933c8622ad2b914.yml -openapi_spec_hash: 2e20722d36e979a3f99c759374900f0f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-284110315b67ae5827ad2ddbdb51aa98b45cb7cc2c692319ec4b7b1280880dea.yml +openapi_spec_hash: 26a0505679bc05d59c9c9c7093cc414b config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 From 9399af448b09b28392d91f980fbf2ef5f54b2364 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 02:15:42 +0000 Subject: [PATCH 19/27] chore(readme): update badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b14f0be0..172b5b8e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Riza Python API library -[![PyPI version](https://img.shields.io/pypi/v/rizaio.svg)](https://pypi.org/project/rizaio/) +[![PyPI version]()](https://pypi.org/project/rizaio/) The Riza Python library provides convenient access to the Riza REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, From 4ebdf17f9eba4d440261e307da12be148e4ab8c8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 05:51:54 +0000 Subject: [PATCH 20/27] fix(tests): fix: tests which call HTTP endpoints directly with the example parameters --- tests/test_client.py | 53 ++++++++++---------------------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index 2c880d24..b474b30a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,9 +23,7 @@ from rizaio import Riza, AsyncRiza, APIResponseValidationError from rizaio._types import Omit -from rizaio._utils import maybe_transform from rizaio._models import BaseModel, FinalRequestOptions -from rizaio._constants import RAW_RESPONSE_HEADER from rizaio._exceptions import RizaError, APIStatusError, APITimeoutError, APIResponseValidationError from rizaio._base_client import ( DEFAULT_TIMEOUT, @@ -35,7 +33,6 @@ DefaultAsyncHttpxClient, make_request_options, ) -from rizaio.types.command_exec_params import CommandExecParams from .utils import update_env @@ -707,36 +704,21 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("rizaio._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Riza) -> None: respx_mock.post("/v1/execute").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/v1/execute", - body=cast( - object, maybe_transform(dict(code="print('Hello, World!')", language="python"), CommandExecParams) - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.command.with_streaming_response.exec(code='print("Hello world!")', language="python").__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("rizaio._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Riza) -> None: respx_mock.post("/v1/execute").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/v1/execute", - body=cast( - object, maybe_transform(dict(code="print('Hello, World!')", language="python"), CommandExecParams) - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + client.command.with_streaming_response.exec(code='print("Hello world!")', language="python").__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1528,36 +1510,25 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("rizaio._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncRiza) -> None: respx_mock.post("/v1/execute").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/v1/execute", - body=cast( - object, maybe_transform(dict(code="print('Hello, World!')", language="python"), CommandExecParams) - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.command.with_streaming_response.exec( + code='print("Hello world!")', language="python" + ).__aenter__() assert _get_open_connections(self.client) == 0 @mock.patch("rizaio._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: + async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncRiza) -> None: respx_mock.post("/v1/execute").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/v1/execute", - body=cast( - object, maybe_transform(dict(code="print('Hello, World!')", language="python"), CommandExecParams) - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + await async_client.command.with_streaming_response.exec( + code='print("Hello world!")', language="python" + ).__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) From 75b4f8d02e06917aa76e2321b65425aa59c7bf57 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 02:53:41 +0000 Subject: [PATCH 21/27] docs(client): fix httpx.Timeout documentation reference --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 172b5b8e..3c1cd30a 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ client.with_options(max_retries=5).command.exec( ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from rizaio import Riza From b433926d78e66e1234125f155aa6f2458be768ea Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 04:23:46 +0000 Subject: [PATCH 22/27] feat(client): add support for aiohttp --- README.md | 35 +++++++++++++++ pyproject.toml | 2 + requirements-dev.lock | 27 ++++++++++++ requirements.lock | 27 ++++++++++++ src/rizaio/__init__.py | 3 +- src/rizaio/_base_client.py | 22 ++++++++++ .../api_resources/runtimes/test_revisions.py | 4 +- tests/api_resources/test_command.py | 4 +- tests/api_resources/test_runtimes.py | 4 +- tests/api_resources/test_secrets.py | 4 +- tests/api_resources/test_tools.py | 4 +- tests/conftest.py | 43 ++++++++++++++++--- 12 files changed, 167 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3c1cd30a..da31a2a6 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,41 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from PyPI +pip install rizaio[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from rizaio import DefaultAioHttpClient +from rizaio import AsyncRiza + + +async def main() -> None: + async with AsyncRiza( + api_key=os.environ.get("RIZA_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + response = await client.command.exec( + code="print('Hello, World!')", + language="python", + ) + print(response.id) + + +asyncio.run(main()) +``` + ## Using types Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: diff --git a/pyproject.toml b/pyproject.toml index a861ab99..146edc35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/riza-io/riza-api-python" Repository = "https://github.com/riza-io/riza-api-python" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index 9019df21..9b2900ca 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,6 +10,13 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via httpx-aiohttp + # via rizaio +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 @@ -17,6 +24,10 @@ anyio==4.4.0 # via rizaio argcomplete==3.1.2 # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -34,16 +45,23 @@ execnet==2.1.1 # via pytest-xdist filelock==3.12.4 # via virtualenv +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 + # via httpx-aiohttp # via respx # via rizaio +httpx-aiohttp==0.1.6 + # via rizaio idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -51,6 +69,9 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py +multidict==6.4.4 + # via aiohttp + # via yarl mypy==1.14.1 mypy-extensions==1.0.0 # via mypy @@ -65,6 +86,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via rizaio pydantic-core==2.27.1 @@ -97,6 +121,7 @@ tomli==2.0.2 # via pytest typing-extensions==4.12.2 # via anyio + # via multidict # via mypy # via pydantic # via pydantic-core @@ -104,5 +129,7 @@ typing-extensions==4.12.2 # via rizaio virtualenv==20.24.5 # via nox +yarl==1.20.0 + # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 4920099f..77a9e6ad 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,11 +10,22 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via httpx-aiohttp + # via rizaio +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via httpx # via rizaio +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -22,15 +33,28 @@ distro==1.8.0 # via rizaio exceptiongroup==1.2.2 # via anyio +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 + # via httpx-aiohttp + # via rizaio +httpx-aiohttp==0.1.6 # via rizaio idna==3.4 # via anyio # via httpx + # via yarl +multidict==6.4.4 + # via aiohttp + # via yarl +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via rizaio pydantic-core==2.27.1 @@ -40,6 +64,9 @@ sniffio==1.3.0 # via rizaio typing-extensions==4.12.2 # via anyio + # via multidict # via pydantic # via pydantic-core # via rizaio +yarl==1.20.0 + # via aiohttp diff --git a/src/rizaio/__init__.py b/src/rizaio/__init__.py index 98970cff..b00802d2 100644 --- a/src/rizaio/__init__.py +++ b/src/rizaio/__init__.py @@ -26,7 +26,7 @@ UnprocessableEntityError, APIResponseValidationError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -68,6 +68,7 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] if not _t.TYPE_CHECKING: diff --git a/src/rizaio/_base_client.py b/src/rizaio/_base_client.py index 54d4f617..8ac84196 100644 --- a/src/rizaio/_base_client.py +++ b/src/rizaio/_base_client.py @@ -1289,6 +1289,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1297,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): diff --git a/tests/api_resources/runtimes/test_revisions.py b/tests/api_resources/runtimes/test_revisions.py index eda2f406..473d264e 100644 --- a/tests/api_resources/runtimes/test_revisions.py +++ b/tests/api_resources/runtimes/test_revisions.py @@ -171,7 +171,9 @@ def test_path_params_get(self, client: Riza) -> None: class TestAsyncRevisions: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncRiza) -> None: diff --git a/tests/api_resources/test_command.py b/tests/api_resources/test_command.py index 8e8c133f..bd71814d 100644 --- a/tests/api_resources/test_command.py +++ b/tests/api_resources/test_command.py @@ -174,7 +174,9 @@ def test_streaming_response_exec_func(self, client: Riza) -> None: class TestAsyncCommand: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_exec(self, async_client: AsyncRiza) -> None: diff --git a/tests/api_resources/test_runtimes.py b/tests/api_resources/test_runtimes.py index f75a5348..29f3de92 100644 --- a/tests/api_resources/test_runtimes.py +++ b/tests/api_resources/test_runtimes.py @@ -151,7 +151,9 @@ def test_path_params_get(self, client: Riza) -> None: class TestAsyncRuntimes: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncRiza) -> None: diff --git a/tests/api_resources/test_secrets.py b/tests/api_resources/test_secrets.py index 77592084..0d90a5fe 100644 --- a/tests/api_resources/test_secrets.py +++ b/tests/api_resources/test_secrets.py @@ -87,7 +87,9 @@ def test_streaming_response_list(self, client: Riza) -> None: class TestAsyncSecrets: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncRiza) -> None: diff --git a/tests/api_resources/test_tools.py b/tests/api_resources/test_tools.py index cc9202fd..31eed1c0 100644 --- a/tests/api_resources/test_tools.py +++ b/tests/api_resources/test_tools.py @@ -271,7 +271,9 @@ def test_path_params_get(self, client: Riza) -> None: class TestAsyncTools: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @parametrize async def test_method_create(self, async_client: AsyncRiza) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 962ca6d9..f028963b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,12 @@ import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from rizaio import Riza, AsyncRiza +from rizaio import Riza, AsyncRiza, DefaultAioHttpClient +from rizaio._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -27,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -45,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Riza]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncRiza]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncRiza(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncRiza( + base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client + ) as client: yield client From afe509f0aad8d8e1f75a4a55af506f7ec178005a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 04:19:28 +0000 Subject: [PATCH 23/27] chore(tests): skip some failing tests on the latest python versions --- tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index b474b30a..32de542b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -191,6 +191,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -985,6 +986,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") From ab3a1f5a2597a2407a96124c5bbd620eb61aed1a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 01:56:21 +0000 Subject: [PATCH 24/27] feat(api): api update --- .github/workflows/ci.yml | 3 +++ .stats.yml | 4 ++-- bin/check-release-environment | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f56b9fd..af61cd8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/riza-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -42,6 +43,7 @@ jobs: contents: read id-token: write runs-on: depot-ubuntu-24.04 + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -62,6 +64,7 @@ jobs: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/riza-api-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 diff --git a/.stats.yml b/.stats.yml index 05ecd3be..53459458 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 15 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-284110315b67ae5827ad2ddbdb51aa98b45cb7cc2c692319ec4b7b1280880dea.yml -openapi_spec_hash: 26a0505679bc05d59c9c9c7093cc414b +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-bab65e0b0c2b228ee407ddd78cdcd7f3708bac6d00c0ada71efc6eef73de20fe.yml +openapi_spec_hash: 08c3a26bba78a202f09b51c2d4eb0001 config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 diff --git a/bin/check-release-environment b/bin/check-release-environment index 32a4a878..b845b0f4 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The RIZA_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi lenErrors=${#errors[@]} From 54b808196ab410711a60e1e6eafc809045255580 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 16:39:53 +0000 Subject: [PATCH 25/27] feat(api): api update --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af61cd8c..5c25245b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,14 +36,13 @@ jobs: run: ./scripts/lint upload: - if: github.repository == 'stainless-sdks/riza-api-python' + if: github.repository == 'stainless-sdks/riza-api-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 name: upload permissions: contents: read id-token: write runs-on: depot-ubuntu-24.04 - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 345e02a9e93c2ca9a26ce0f884dc9a018cd495cd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:09:26 +0000 Subject: [PATCH 26/27] feat(api): api update --- .stats.yml | 4 +- api.md | 16 +- src/rizaio/_client.py | 10 +- src/rizaio/pagination.py | 55 +++ src/rizaio/resources/__init__.py | 14 + src/rizaio/resources/executions.py | 284 ++++++++++++ src/rizaio/resources/runtimes/runtimes.py | 79 ++++ src/rizaio/types/__init__.py | 3 + src/rizaio/types/execution.py | 455 ++++++++++++++++++++ src/rizaio/types/execution_list_params.py | 25 ++ src/rizaio/types/runtime_delete_response.py | 13 + tests/api_resources/test_executions.py | 169 ++++++++ tests/api_resources/test_runtimes.py | 78 +++- 13 files changed, 1200 insertions(+), 5 deletions(-) create mode 100644 src/rizaio/resources/executions.py create mode 100644 src/rizaio/types/execution.py create mode 100644 src/rizaio/types/execution_list_params.py create mode 100644 src/rizaio/types/runtime_delete_response.py create mode 100644 tests/api_resources/test_executions.py diff --git a/.stats.yml b/.stats.yml index 53459458..ccef6be0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 15 +configured_endpoints: 18 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/riza%2Friza-api-bab65e0b0c2b228ee407ddd78cdcd7f3708bac6d00c0ada71efc6eef73de20fe.yml openapi_spec_hash: 08c3a26bba78a202f09b51c2d4eb0001 -config_hash: 8ac0e6ef0ce0f5388eed4f14e515a7c9 +config_hash: cba1727a13fc23eaeb011480ac88a06d diff --git a/api.md b/api.md index be15f787..29fc5d8c 100644 --- a/api.md +++ b/api.md @@ -45,13 +45,14 @@ Methods: Types: ```python -from rizaio.types import Runtime +from rizaio.types import Runtime, RuntimeDeleteResponse ``` Methods: - client.runtimes.create(\*\*params) -> Runtime - client.runtimes.list(\*\*params) -> SyncRuntimesPagination[Runtime] +- client.runtimes.delete(id) -> RuntimeDeleteResponse - client.runtimes.get(id) -> Runtime ## Revisions @@ -67,3 +68,16 @@ Methods: - client.runtimes.revisions.create(id, \*\*params) -> Revision - client.runtimes.revisions.list(id) -> RevisionListResponse - client.runtimes.revisions.get(revision_id, \*, runtime_id) -> Revision + +# Executions + +Types: + +```python +from rizaio.types import Execution +``` + +Methods: + +- client.executions.list(\*\*params) -> SyncDefaultPagination[Execution] +- client.executions.get(id) -> Execution diff --git a/src/rizaio/_client.py b/src/rizaio/_client.py index 02c7536b..f97a7f78 100644 --- a/src/rizaio/_client.py +++ b/src/rizaio/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import tools, command, secrets +from .resources import tools, command, secrets, executions from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import RizaError, APIStatusError from ._base_client import ( @@ -39,6 +39,7 @@ class Riza(SyncAPIClient): tools: tools.ToolsResource command: command.CommandResource runtimes: runtimes.RuntimesResource + executions: executions.ExecutionsResource with_raw_response: RizaWithRawResponse with_streaming_response: RizaWithStreamedResponse @@ -100,6 +101,7 @@ def __init__( self.tools = tools.ToolsResource(self) self.command = command.CommandResource(self) self.runtimes = runtimes.RuntimesResource(self) + self.executions = executions.ExecutionsResource(self) self.with_raw_response = RizaWithRawResponse(self) self.with_streaming_response = RizaWithStreamedResponse(self) @@ -213,6 +215,7 @@ class AsyncRiza(AsyncAPIClient): tools: tools.AsyncToolsResource command: command.AsyncCommandResource runtimes: runtimes.AsyncRuntimesResource + executions: executions.AsyncExecutionsResource with_raw_response: AsyncRizaWithRawResponse with_streaming_response: AsyncRizaWithStreamedResponse @@ -274,6 +277,7 @@ def __init__( self.tools = tools.AsyncToolsResource(self) self.command = command.AsyncCommandResource(self) self.runtimes = runtimes.AsyncRuntimesResource(self) + self.executions = executions.AsyncExecutionsResource(self) self.with_raw_response = AsyncRizaWithRawResponse(self) self.with_streaming_response = AsyncRizaWithStreamedResponse(self) @@ -388,6 +392,7 @@ def __init__(self, client: Riza) -> None: self.tools = tools.ToolsResourceWithRawResponse(client.tools) self.command = command.CommandResourceWithRawResponse(client.command) self.runtimes = runtimes.RuntimesResourceWithRawResponse(client.runtimes) + self.executions = executions.ExecutionsResourceWithRawResponse(client.executions) class AsyncRizaWithRawResponse: @@ -396,6 +401,7 @@ def __init__(self, client: AsyncRiza) -> None: self.tools = tools.AsyncToolsResourceWithRawResponse(client.tools) self.command = command.AsyncCommandResourceWithRawResponse(client.command) self.runtimes = runtimes.AsyncRuntimesResourceWithRawResponse(client.runtimes) + self.executions = executions.AsyncExecutionsResourceWithRawResponse(client.executions) class RizaWithStreamedResponse: @@ -404,6 +410,7 @@ def __init__(self, client: Riza) -> None: self.tools = tools.ToolsResourceWithStreamingResponse(client.tools) self.command = command.CommandResourceWithStreamingResponse(client.command) self.runtimes = runtimes.RuntimesResourceWithStreamingResponse(client.runtimes) + self.executions = executions.ExecutionsResourceWithStreamingResponse(client.executions) class AsyncRizaWithStreamedResponse: @@ -412,6 +419,7 @@ def __init__(self, client: AsyncRiza) -> None: self.tools = tools.AsyncToolsResourceWithStreamingResponse(client.tools) self.command = command.AsyncCommandResourceWithStreamingResponse(client.command) self.runtimes = runtimes.AsyncRuntimesResourceWithStreamingResponse(client.runtimes) + self.executions = executions.AsyncExecutionsResourceWithStreamingResponse(client.executions) Client = Riza diff --git a/src/rizaio/pagination.py b/src/rizaio/pagination.py index f8135a07..89949b4c 100644 --- a/src/rizaio/pagination.py +++ b/src/rizaio/pagination.py @@ -6,6 +6,8 @@ from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage __all__ = [ + "SyncDefaultPagination", + "AsyncDefaultPagination", "SyncRuntimesPagination", "AsyncRuntimesPagination", "SyncToolsPagination", @@ -17,6 +19,11 @@ _T = TypeVar("_T") +@runtime_checkable +class DefaultPaginationItem(Protocol): + id: str + + @runtime_checkable class RuntimesPaginationItem(Protocol): id: str @@ -32,6 +39,54 @@ class SecretsPaginationItem(Protocol): id: str +class SyncDefaultPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def next_page_info(self) -> Optional[PageInfo]: + data = self.data + if not data: + return None + + item = cast(Any, data[-1]) + if not isinstance(item, DefaultPaginationItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison] + # TODO emit warning log + return None + + return PageInfo(params={"starting_after": item.id}) + + +class AsyncDefaultPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]): + data: List[_T] + + @override + def _get_page_items(self) -> List[_T]: + data = self.data + if not data: + return [] + return data + + @override + def next_page_info(self) -> Optional[PageInfo]: + data = self.data + if not data: + return None + + item = cast(Any, data[-1]) + if not isinstance(item, DefaultPaginationItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison] + # TODO emit warning log + return None + + return PageInfo(params={"starting_after": item.id}) + + class SyncRuntimesPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]): runtimes: List[_T] diff --git a/src/rizaio/resources/__init__.py b/src/rizaio/resources/__init__.py index 23c10b05..3185b1d0 100644 --- a/src/rizaio/resources/__init__.py +++ b/src/rizaio/resources/__init__.py @@ -32,6 +32,14 @@ RuntimesResourceWithStreamingResponse, AsyncRuntimesResourceWithStreamingResponse, ) +from .executions import ( + ExecutionsResource, + AsyncExecutionsResource, + ExecutionsResourceWithRawResponse, + AsyncExecutionsResourceWithRawResponse, + ExecutionsResourceWithStreamingResponse, + AsyncExecutionsResourceWithStreamingResponse, +) __all__ = [ "SecretsResource", @@ -58,4 +66,10 @@ "AsyncRuntimesResourceWithRawResponse", "RuntimesResourceWithStreamingResponse", "AsyncRuntimesResourceWithStreamingResponse", + "ExecutionsResource", + "AsyncExecutionsResource", + "ExecutionsResourceWithRawResponse", + "AsyncExecutionsResourceWithRawResponse", + "ExecutionsResourceWithStreamingResponse", + "AsyncExecutionsResourceWithStreamingResponse", ] diff --git a/src/rizaio/resources/executions.py b/src/rizaio/resources/executions.py new file mode 100644 index 00000000..43f3c9bf --- /dev/null +++ b/src/rizaio/resources/executions.py @@ -0,0 +1,284 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..types import execution_list_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._utils import maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncDefaultPagination, AsyncDefaultPagination +from .._base_client import AsyncPaginator, make_request_options +from ..types.execution import Execution + +__all__ = ["ExecutionsResource", "AsyncExecutionsResource"] + + +class ExecutionsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> ExecutionsResourceWithRawResponse: + """ + 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/riza-io/riza-api-python#accessing-raw-response-data-eg-headers + """ + return ExecutionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> ExecutionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/riza-io/riza-api-python#with_streaming_response + """ + return ExecutionsResourceWithStreamingResponse(self) + + def list( + self, + *, + limit: int | NotGiven = NOT_GIVEN, + only_non_zero_exit_codes: bool | NotGiven = NOT_GIVEN, + starting_after: 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> SyncDefaultPagination[Execution]: + """ + Returns a list of executions in your project. + + Args: + limit: The number of items to return. Defaults to 100. Maximum is 100. + + only_non_zero_exit_codes: If true, only show executions where the exit code is not 0, indicating an + execution error. Defaults to false. + + starting_after: The ID of the item to start after. To get the next page of results, set this to + the ID of the last item in the current page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/executions", + page=SyncDefaultPagination[Execution], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "only_non_zero_exit_codes": only_non_zero_exit_codes, + "starting_after": starting_after, + }, + execution_list_params.ExecutionListParams, + ), + ), + model=Execution, + ) + + def get( + self, + id: 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, + ) -> Execution: + """ + Retrieves an execution. + + 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}") + return self._get( + f"/v1/executions/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Execution, + ) + + +class AsyncExecutionsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncExecutionsResourceWithRawResponse: + """ + 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/riza-io/riza-api-python#accessing-raw-response-data-eg-headers + """ + return AsyncExecutionsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncExecutionsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/riza-io/riza-api-python#with_streaming_response + """ + return AsyncExecutionsResourceWithStreamingResponse(self) + + def list( + self, + *, + limit: int | NotGiven = NOT_GIVEN, + only_non_zero_exit_codes: bool | NotGiven = NOT_GIVEN, + starting_after: 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. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> AsyncPaginator[Execution, AsyncDefaultPagination[Execution]]: + """ + Returns a list of executions in your project. + + Args: + limit: The number of items to return. Defaults to 100. Maximum is 100. + + only_non_zero_exit_codes: If true, only show executions where the exit code is not 0, indicating an + execution error. Defaults to false. + + starting_after: The ID of the item to start after. To get the next page of results, set this to + the ID of the last item in the current page. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._get_api_list( + "/v1/executions", + page=AsyncDefaultPagination[Execution], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "limit": limit, + "only_non_zero_exit_codes": only_non_zero_exit_codes, + "starting_after": starting_after, + }, + execution_list_params.ExecutionListParams, + ), + ), + model=Execution, + ) + + async def get( + self, + id: 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, + ) -> Execution: + """ + Retrieves an execution. + + 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}") + return await self._get( + f"/v1/executions/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Execution, + ) + + +class ExecutionsResourceWithRawResponse: + def __init__(self, executions: ExecutionsResource) -> None: + self._executions = executions + + self.list = to_raw_response_wrapper( + executions.list, + ) + self.get = to_raw_response_wrapper( + executions.get, + ) + + +class AsyncExecutionsResourceWithRawResponse: + def __init__(self, executions: AsyncExecutionsResource) -> None: + self._executions = executions + + self.list = async_to_raw_response_wrapper( + executions.list, + ) + self.get = async_to_raw_response_wrapper( + executions.get, + ) + + +class ExecutionsResourceWithStreamingResponse: + def __init__(self, executions: ExecutionsResource) -> None: + self._executions = executions + + self.list = to_streamed_response_wrapper( + executions.list, + ) + self.get = to_streamed_response_wrapper( + executions.get, + ) + + +class AsyncExecutionsResourceWithStreamingResponse: + def __init__(self, executions: AsyncExecutionsResource) -> None: + self._executions = executions + + self.list = async_to_streamed_response_wrapper( + executions.list, + ) + self.get = async_to_streamed_response_wrapper( + executions.get, + ) diff --git a/src/rizaio/resources/runtimes/runtimes.py b/src/rizaio/resources/runtimes/runtimes.py index db5e3ea3..522cf696 100644 --- a/src/rizaio/resources/runtimes/runtimes.py +++ b/src/rizaio/resources/runtimes/runtimes.py @@ -28,6 +28,7 @@ from ...pagination import SyncRuntimesPagination, AsyncRuntimesPagination from ..._base_client import AsyncPaginator, make_request_options from ...types.runtime import Runtime +from ...types.runtime_delete_response import RuntimeDeleteResponse __all__ = ["RuntimesResource", "AsyncRuntimesResource"] @@ -149,6 +150,39 @@ def list( model=Runtime, ) + def delete( + self, + id: 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, + ) -> RuntimeDeleteResponse: + """ + Deletes a runtime. + + 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}") + return self._delete( + f"/v1/runtimes/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuntimeDeleteResponse, + ) + def get( self, id: str, @@ -300,6 +334,39 @@ def list( model=Runtime, ) + async def delete( + self, + id: 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, + ) -> RuntimeDeleteResponse: + """ + Deletes a runtime. + + 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}") + return await self._delete( + f"/v1/runtimes/{id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=RuntimeDeleteResponse, + ) + async def get( self, id: str, @@ -344,6 +411,9 @@ def __init__(self, runtimes: RuntimesResource) -> None: self.list = to_raw_response_wrapper( runtimes.list, ) + self.delete = to_raw_response_wrapper( + runtimes.delete, + ) self.get = to_raw_response_wrapper( runtimes.get, ) @@ -363,6 +433,9 @@ def __init__(self, runtimes: AsyncRuntimesResource) -> None: self.list = async_to_raw_response_wrapper( runtimes.list, ) + self.delete = async_to_raw_response_wrapper( + runtimes.delete, + ) self.get = async_to_raw_response_wrapper( runtimes.get, ) @@ -382,6 +455,9 @@ def __init__(self, runtimes: RuntimesResource) -> None: self.list = to_streamed_response_wrapper( runtimes.list, ) + self.delete = to_streamed_response_wrapper( + runtimes.delete, + ) self.get = to_streamed_response_wrapper( runtimes.get, ) @@ -401,6 +477,9 @@ def __init__(self, runtimes: AsyncRuntimesResource) -> None: self.list = async_to_streamed_response_wrapper( runtimes.list, ) + self.delete = async_to_streamed_response_wrapper( + runtimes.delete, + ) self.get = async_to_streamed_response_wrapper( runtimes.get, ) diff --git a/src/rizaio/types/__init__.py b/src/rizaio/types/__init__.py index f4bc20d8..eacdc7ed 100644 --- a/src/rizaio/types/__init__.py +++ b/src/rizaio/types/__init__.py @@ -5,6 +5,7 @@ from .tool import Tool as Tool from .secret import Secret as Secret from .runtime import Runtime as Runtime +from .execution import Execution as Execution from .tool_exec_params import ToolExecParams as ToolExecParams from .tool_list_params import ToolListParams as ToolListParams from .secret_list_params import SecretListParams as SecretListParams @@ -15,6 +16,8 @@ from .runtime_list_params import RuntimeListParams as RuntimeListParams from .secret_create_params import SecretCreateParams as SecretCreateParams from .command_exec_response import CommandExecResponse as CommandExecResponse +from .execution_list_params import ExecutionListParams as ExecutionListParams from .runtime_create_params import RuntimeCreateParams as RuntimeCreateParams +from .runtime_delete_response import RuntimeDeleteResponse as RuntimeDeleteResponse from .command_exec_func_params import CommandExecFuncParams as CommandExecFuncParams from .command_exec_func_response import CommandExecFuncResponse as CommandExecFuncResponse diff --git a/src/rizaio/types/execution.py b/src/rizaio/types/execution.py new file mode 100644 index 00000000..8fb6375f --- /dev/null +++ b/src/rizaio/types/execution.py @@ -0,0 +1,455 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Annotated, TypeAlias + +from .._utils import PropertyInfo +from .._models import BaseModel + +__all__ = [ + "Execution", + "Details", + "DetailsToolExecutionDetails", + "DetailsToolExecutionDetailsRequest", + "DetailsToolExecutionDetailsRequestEnv", + "DetailsToolExecutionDetailsRequestHTTP", + "DetailsToolExecutionDetailsRequestHTTPAllow", + "DetailsToolExecutionDetailsRequestHTTPAllowAuth", + "DetailsToolExecutionDetailsRequestHTTPAllowAuthBasic", + "DetailsToolExecutionDetailsRequestHTTPAllowAuthBearer", + "DetailsToolExecutionDetailsRequestHTTPAllowAuthQuery", + "DetailsToolExecutionDetailsResponse", + "DetailsToolExecutionDetailsResponseExecution", + "DetailsFunctionExecutionDetails", + "DetailsFunctionExecutionDetailsRequest", + "DetailsFunctionExecutionDetailsRequestFile", + "DetailsFunctionExecutionDetailsRequestHTTP", + "DetailsFunctionExecutionDetailsRequestHTTPAllow", + "DetailsFunctionExecutionDetailsRequestHTTPAllowAuth", + "DetailsFunctionExecutionDetailsRequestHTTPAllowAuthBasic", + "DetailsFunctionExecutionDetailsRequestHTTPAllowAuthBearer", + "DetailsFunctionExecutionDetailsRequestHTTPAllowAuthHeader", + "DetailsFunctionExecutionDetailsRequestHTTPAllowAuthQuery", + "DetailsFunctionExecutionDetailsRequestLimits", + "DetailsFunctionExecutionDetailsResponse", + "DetailsFunctionExecutionDetailsResponseExecution", + "DetailsScriptExecutionDetails", + "DetailsScriptExecutionDetailsRequest", + "DetailsScriptExecutionDetailsRequestFile", + "DetailsScriptExecutionDetailsRequestHTTP", + "DetailsScriptExecutionDetailsRequestHTTPAllow", + "DetailsScriptExecutionDetailsRequestHTTPAllowAuth", + "DetailsScriptExecutionDetailsRequestHTTPAllowAuthBasic", + "DetailsScriptExecutionDetailsRequestHTTPAllowAuthBearer", + "DetailsScriptExecutionDetailsRequestHTTPAllowAuthHeader", + "DetailsScriptExecutionDetailsRequestHTTPAllowAuthQuery", + "DetailsScriptExecutionDetailsRequestLimits", + "DetailsScriptExecutionDetailsResponse", +] + + +class DetailsToolExecutionDetailsRequestEnv(BaseModel): + name: str + + secret_id: Optional[str] = None + + value: Optional[str] = None + + +class DetailsToolExecutionDetailsRequestHTTPAllowAuthBasic(BaseModel): + password: Optional[str] = None + + secret_id: Optional[str] = None + + user_id: Optional[str] = None + + +class DetailsToolExecutionDetailsRequestHTTPAllowAuthBearer(BaseModel): + token: Optional[str] = None + """The token to set, e.g. 'Authorization: Bearer '.""" + + secret_id: Optional[str] = None + + +class DetailsToolExecutionDetailsRequestHTTPAllowAuthQuery(BaseModel): + key: Optional[str] = None + + secret_id: Optional[str] = None + + value: Optional[str] = None + + +class DetailsToolExecutionDetailsRequestHTTPAllowAuth(BaseModel): + basic: Optional[DetailsToolExecutionDetailsRequestHTTPAllowAuthBasic] = None + + bearer: Optional[DetailsToolExecutionDetailsRequestHTTPAllowAuthBearer] = None + """Configuration to add an 'Authorization' header using the 'Bearer' scheme.""" + + query: Optional[DetailsToolExecutionDetailsRequestHTTPAllowAuthQuery] = None + + +class DetailsToolExecutionDetailsRequestHTTPAllow(BaseModel): + auth: Optional[DetailsToolExecutionDetailsRequestHTTPAllowAuth] = None + """Authentication configuration for outbound requests to this host.""" + + host: Optional[str] = None + """The hostname to allow.""" + + +class DetailsToolExecutionDetailsRequestHTTP(BaseModel): + allow: Optional[List[DetailsToolExecutionDetailsRequestHTTPAllow]] = None + """List of allowed HTTP hosts and associated authentication.""" + + +class DetailsToolExecutionDetailsRequest(BaseModel): + env: Optional[List[DetailsToolExecutionDetailsRequestEnv]] = None + """Set of key-value pairs to add to the tool's execution environment.""" + + http: Optional[DetailsToolExecutionDetailsRequestHTTP] = None + """Configuration for HTTP requests and authentication.""" + + input: Optional[object] = None + """The input to the tool. + + This must be a valid JSON-serializable object. It will be validated against the + tool's input schema. + """ + + revision_id: Optional[str] = None + """The Tool revision ID to execute. + + This optional parmeter is used to pin executions to specific versions of the + Tool. If not provided, the latest (current) version of the Tool will be + executed. + """ + + +class DetailsToolExecutionDetailsResponseExecution(BaseModel): + id: str + """The ID of the execution.""" + + duration: int + """The execution time of the function in milliseconds.""" + + exit_code: int + """The exit code returned by the function. + + Will often be '0' on success and non-zero on failure. + """ + + stderr: str + """The contents of 'stderr' after executing the function.""" + + stdout: str + """The contents of 'stdout' after executing the function.""" + + +class DetailsToolExecutionDetailsResponse(BaseModel): + execution: DetailsToolExecutionDetailsResponseExecution + """The execution details of the function.""" + + output: object + """The returned value of the Tool's execute function.""" + + output_status: Literal["error", "json_serialization_error", "valid"] + """The status of the output. + + "valid" means your Tool executed successfully and returned a valid + JSON-serializable object, or void. "json_serialization_error" means your Tool + executed successfully, but returned a nonserializable object. "error" means your + Tool failed to execute. + """ + + +class DetailsToolExecutionDetails(BaseModel): + request: DetailsToolExecutionDetailsRequest + + response: DetailsToolExecutionDetailsResponse + + tool_id: str + + type: Literal["tool"] + + +class DetailsFunctionExecutionDetailsRequestFile(BaseModel): + contents: Optional[str] = None + """The contents of the file.""" + + path: Optional[str] = None + """The relative path of the file.""" + + +class DetailsFunctionExecutionDetailsRequestHTTPAllowAuthBasic(BaseModel): + password: Optional[str] = None + + user_id: Optional[str] = None + + +class DetailsFunctionExecutionDetailsRequestHTTPAllowAuthBearer(BaseModel): + token: Optional[str] = None + """The token to set, e.g. 'Authorization: Bearer '.""" + + +class DetailsFunctionExecutionDetailsRequestHTTPAllowAuthHeader(BaseModel): + name: Optional[str] = None + + value: Optional[str] = None + + +class DetailsFunctionExecutionDetailsRequestHTTPAllowAuthQuery(BaseModel): + key: Optional[str] = None + + value: Optional[str] = None + + +class DetailsFunctionExecutionDetailsRequestHTTPAllowAuth(BaseModel): + basic: Optional[DetailsFunctionExecutionDetailsRequestHTTPAllowAuthBasic] = None + + bearer: Optional[DetailsFunctionExecutionDetailsRequestHTTPAllowAuthBearer] = None + """Configuration to add an 'Authorization' header using the 'Bearer' scheme.""" + + header: Optional[DetailsFunctionExecutionDetailsRequestHTTPAllowAuthHeader] = None + + query: Optional[DetailsFunctionExecutionDetailsRequestHTTPAllowAuthQuery] = None + + +class DetailsFunctionExecutionDetailsRequestHTTPAllow(BaseModel): + auth: Optional[DetailsFunctionExecutionDetailsRequestHTTPAllowAuth] = None + """Authentication configuration for outbound requests to this host.""" + + host: Optional[str] = None + """The hostname to allow.""" + + +class DetailsFunctionExecutionDetailsRequestHTTP(BaseModel): + allow: Optional[List[DetailsFunctionExecutionDetailsRequestHTTPAllow]] = None + """List of allowed HTTP hosts and associated authentication.""" + + +class DetailsFunctionExecutionDetailsRequestLimits(BaseModel): + execution_timeout: Optional[int] = None + """The maximum time allowed for execution (in seconds). Default is 30.""" + + memory_size: Optional[int] = None + """The maximum memory allowed for execution (in MiB). Default is 128.""" + + +class DetailsFunctionExecutionDetailsRequest(BaseModel): + code: str + """The function to execute. + + Your code must define a function named "execute" that takes in a single argument + and returns a JSON-serializable value. + """ + + language: Literal["python", "javascript", "typescript"] + """The interpreter to use when executing code.""" + + env: Optional[Dict[str, str]] = None + """Set of key-value pairs to add to the function's execution environment.""" + + files: Optional[List[DetailsFunctionExecutionDetailsRequestFile]] = None + """List of input files.""" + + http: Optional[DetailsFunctionExecutionDetailsRequestHTTP] = None + """Configuration for HTTP requests and authentication.""" + + input: Optional[object] = None + """The input to the function. + + This must be a valid JSON-serializable object. If you do not pass an input, your + function will be called with None (Python) or null (JavaScript/TypeScript) as + the argument. + """ + + limits: Optional[DetailsFunctionExecutionDetailsRequestLimits] = None + """Configuration for execution environment limits.""" + + runtime_revision_id: Optional[str] = None + """The ID of the runtime revision to use when executing code.""" + + +class DetailsFunctionExecutionDetailsResponseExecution(BaseModel): + id: str + """The ID of the execution.""" + + duration: int + """The execution time of the function in milliseconds.""" + + exit_code: int + """The exit code returned by the function. + + Will often be '0' on success and non-zero on failure. + """ + + stderr: str + """The contents of 'stderr' after executing the function.""" + + stdout: str + """The contents of 'stdout' after executing the function.""" + + +class DetailsFunctionExecutionDetailsResponse(BaseModel): + execution: DetailsFunctionExecutionDetailsResponseExecution + """The execution details of the function.""" + + output: object + """The output of the function.""" + + output_status: Literal["error", "json_serialization_error", "valid"] + """The status of the output. + + "valid" means your function executed successfully and returned a valid + JSON-serializable object, or void. "json_serialization_error" means your + function executed successfully, but returned a nonserializable object. "error" + means your function failed to execute. + """ + + +class DetailsFunctionExecutionDetails(BaseModel): + request: DetailsFunctionExecutionDetailsRequest + + response: DetailsFunctionExecutionDetailsResponse + + type: Literal["function"] + + +class DetailsScriptExecutionDetailsRequestFile(BaseModel): + contents: Optional[str] = None + """The contents of the file.""" + + path: Optional[str] = None + """The relative path of the file.""" + + +class DetailsScriptExecutionDetailsRequestHTTPAllowAuthBasic(BaseModel): + password: Optional[str] = None + + user_id: Optional[str] = None + + +class DetailsScriptExecutionDetailsRequestHTTPAllowAuthBearer(BaseModel): + token: Optional[str] = None + """The token to set, e.g. 'Authorization: Bearer '.""" + + +class DetailsScriptExecutionDetailsRequestHTTPAllowAuthHeader(BaseModel): + name: Optional[str] = None + + value: Optional[str] = None + + +class DetailsScriptExecutionDetailsRequestHTTPAllowAuthQuery(BaseModel): + key: Optional[str] = None + + value: Optional[str] = None + + +class DetailsScriptExecutionDetailsRequestHTTPAllowAuth(BaseModel): + basic: Optional[DetailsScriptExecutionDetailsRequestHTTPAllowAuthBasic] = None + + bearer: Optional[DetailsScriptExecutionDetailsRequestHTTPAllowAuthBearer] = None + """Configuration to add an 'Authorization' header using the 'Bearer' scheme.""" + + header: Optional[DetailsScriptExecutionDetailsRequestHTTPAllowAuthHeader] = None + + query: Optional[DetailsScriptExecutionDetailsRequestHTTPAllowAuthQuery] = None + + +class DetailsScriptExecutionDetailsRequestHTTPAllow(BaseModel): + auth: Optional[DetailsScriptExecutionDetailsRequestHTTPAllowAuth] = None + """Authentication configuration for outbound requests to this host.""" + + host: Optional[str] = None + """The hostname to allow.""" + + +class DetailsScriptExecutionDetailsRequestHTTP(BaseModel): + allow: Optional[List[DetailsScriptExecutionDetailsRequestHTTPAllow]] = None + """List of allowed HTTP hosts and associated authentication.""" + + +class DetailsScriptExecutionDetailsRequestLimits(BaseModel): + execution_timeout: Optional[int] = None + """The maximum time allowed for execution (in seconds). Default is 30.""" + + memory_size: Optional[int] = None + """The maximum memory allowed for execution (in MiB). Default is 128.""" + + +class DetailsScriptExecutionDetailsRequest(BaseModel): + code: str + """The code to execute.""" + + language: Literal["python", "javascript", "typescript", "ruby", "php"] + """The interpreter to use when executing code.""" + + args: Optional[List[str]] = None + """List of command line arguments to pass to the script.""" + + env: Optional[Dict[str, str]] = None + """Set of key-value pairs to add to the script's execution environment.""" + + files: Optional[List[DetailsScriptExecutionDetailsRequestFile]] = None + """List of input files.""" + + http: Optional[DetailsScriptExecutionDetailsRequestHTTP] = None + """Configuration for HTTP requests and authentication.""" + + limits: Optional[DetailsScriptExecutionDetailsRequestLimits] = None + """Configuration for execution environment limits.""" + + runtime_revision_id: Optional[str] = None + """The ID of the runtime revision to use when executing code.""" + + stdin: Optional[str] = None + """Input made available to the script via 'stdin'.""" + + +class DetailsScriptExecutionDetailsResponse(BaseModel): + id: str + """The ID of the execution.""" + + duration: int + """The execution time of the script in milliseconds.""" + + exit_code: int + """The exit code returned by the script. + + Will often be '0' on success and non-zero on failure. + """ + + stderr: str + """The contents of 'stderr' after executing the script.""" + + stdout: str + """The contents of 'stdout' after executing the script.""" + + +class DetailsScriptExecutionDetails(BaseModel): + request: DetailsScriptExecutionDetailsRequest + + response: DetailsScriptExecutionDetailsResponse + + type: Literal["script"] + + +Details: TypeAlias = Annotated[ + Union[DetailsToolExecutionDetails, DetailsFunctionExecutionDetails, DetailsScriptExecutionDetails], + PropertyInfo(discriminator="type"), +] + + +class Execution(BaseModel): + id: str + + duration: int + + exit_code: int + + language: Literal["python", "javascript", "typescript", "ruby", "php"] + + started_at: datetime + + details: Optional[Details] = None diff --git a/src/rizaio/types/execution_list_params.py b/src/rizaio/types/execution_list_params.py new file mode 100644 index 00000000..42b9fffa --- /dev/null +++ b/src/rizaio/types/execution_list_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 TypedDict + +__all__ = ["ExecutionListParams"] + + +class ExecutionListParams(TypedDict, total=False): + limit: int + """The number of items to return. Defaults to 100. Maximum is 100.""" + + only_non_zero_exit_codes: bool + """ + If true, only show executions where the exit code is not 0, indicating an + execution error. Defaults to false. + """ + + starting_after: str + """The ID of the item to start after. + + To get the next page of results, set this to the ID of the last item in the + current page. + """ diff --git a/src/rizaio/types/runtime_delete_response.py b/src/rizaio/types/runtime_delete_response.py new file mode 100644 index 00000000..646f83d1 --- /dev/null +++ b/src/rizaio/types/runtime_delete_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["RuntimeDeleteResponse"] + + +class RuntimeDeleteResponse(BaseModel): + id: Optional[str] = None + + deleted: Optional[bool] = None diff --git a/tests/api_resources/test_executions.py b/tests/api_resources/test_executions.py new file mode 100644 index 00000000..7cc3c493 --- /dev/null +++ b/tests/api_resources/test_executions.py @@ -0,0 +1,169 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from rizaio import Riza, AsyncRiza +from tests.utils import assert_matches_type +from rizaio.types import Execution +from rizaio.pagination import SyncDefaultPagination, AsyncDefaultPagination + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestExecutions: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Riza) -> None: + execution = client.executions.list() + assert_matches_type(SyncDefaultPagination[Execution], execution, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Riza) -> None: + execution = client.executions.list( + limit=0, + only_non_zero_exit_codes=True, + starting_after="starting_after", + ) + assert_matches_type(SyncDefaultPagination[Execution], execution, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Riza) -> None: + response = client.executions.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + execution = response.parse() + assert_matches_type(SyncDefaultPagination[Execution], execution, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Riza) -> None: + with client.executions.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + execution = response.parse() + assert_matches_type(SyncDefaultPagination[Execution], execution, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_get(self, client: Riza) -> None: + execution = client.executions.get( + "id", + ) + assert_matches_type(Execution, execution, path=["response"]) + + @parametrize + def test_raw_response_get(self, client: Riza) -> None: + response = client.executions.with_raw_response.get( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + execution = response.parse() + assert_matches_type(Execution, execution, path=["response"]) + + @parametrize + def test_streaming_response_get(self, client: Riza) -> None: + with client.executions.with_streaming_response.get( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + execution = response.parse() + assert_matches_type(Execution, execution, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_get(self, client: Riza) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.executions.with_raw_response.get( + "", + ) + + +class TestAsyncExecutions: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncRiza) -> None: + execution = await async_client.executions.list() + assert_matches_type(AsyncDefaultPagination[Execution], execution, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncRiza) -> None: + execution = await async_client.executions.list( + limit=0, + only_non_zero_exit_codes=True, + starting_after="starting_after", + ) + assert_matches_type(AsyncDefaultPagination[Execution], execution, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncRiza) -> None: + response = await async_client.executions.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + execution = await response.parse() + assert_matches_type(AsyncDefaultPagination[Execution], execution, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncRiza) -> None: + async with async_client.executions.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + execution = await response.parse() + assert_matches_type(AsyncDefaultPagination[Execution], execution, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_get(self, async_client: AsyncRiza) -> None: + execution = await async_client.executions.get( + "id", + ) + assert_matches_type(Execution, execution, path=["response"]) + + @parametrize + async def test_raw_response_get(self, async_client: AsyncRiza) -> None: + response = await async_client.executions.with_raw_response.get( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + execution = await response.parse() + assert_matches_type(Execution, execution, path=["response"]) + + @parametrize + async def test_streaming_response_get(self, async_client: AsyncRiza) -> None: + async with async_client.executions.with_streaming_response.get( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + execution = await response.parse() + assert_matches_type(Execution, execution, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_get(self, async_client: AsyncRiza) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.executions.with_raw_response.get( + "", + ) diff --git a/tests/api_resources/test_runtimes.py b/tests/api_resources/test_runtimes.py index 29f3de92..12554478 100644 --- a/tests/api_resources/test_runtimes.py +++ b/tests/api_resources/test_runtimes.py @@ -9,7 +9,7 @@ from rizaio import Riza, AsyncRiza from tests.utils import assert_matches_type -from rizaio.types import Runtime +from rizaio.types import Runtime, RuntimeDeleteResponse from rizaio.pagination import SyncRuntimesPagination, AsyncRuntimesPagination base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -111,6 +111,44 @@ def test_streaming_response_list(self, client: Riza) -> None: assert cast(Any, response.is_closed) is True + @parametrize + def test_method_delete(self, client: Riza) -> None: + runtime = client.runtimes.delete( + "id", + ) + assert_matches_type(RuntimeDeleteResponse, runtime, path=["response"]) + + @parametrize + def test_raw_response_delete(self, client: Riza) -> None: + response = client.runtimes.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + runtime = response.parse() + assert_matches_type(RuntimeDeleteResponse, runtime, path=["response"]) + + @parametrize + def test_streaming_response_delete(self, client: Riza) -> None: + with client.runtimes.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + runtime = response.parse() + assert_matches_type(RuntimeDeleteResponse, runtime, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_delete(self, client: Riza) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.runtimes.with_raw_response.delete( + "", + ) + @parametrize def test_method_get(self, client: Riza) -> None: runtime = client.runtimes.get( @@ -248,6 +286,44 @@ async def test_streaming_response_list(self, async_client: AsyncRiza) -> None: assert cast(Any, response.is_closed) is True + @parametrize + async def test_method_delete(self, async_client: AsyncRiza) -> None: + runtime = await async_client.runtimes.delete( + "id", + ) + assert_matches_type(RuntimeDeleteResponse, runtime, path=["response"]) + + @parametrize + async def test_raw_response_delete(self, async_client: AsyncRiza) -> None: + response = await async_client.runtimes.with_raw_response.delete( + "id", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + runtime = await response.parse() + assert_matches_type(RuntimeDeleteResponse, runtime, path=["response"]) + + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncRiza) -> None: + async with async_client.runtimes.with_streaming_response.delete( + "id", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + runtime = await response.parse() + assert_matches_type(RuntimeDeleteResponse, runtime, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_delete(self, async_client: AsyncRiza) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.runtimes.with_raw_response.delete( + "", + ) + @parametrize async def test_method_get(self, async_client: AsyncRiza) -> None: runtime = await async_client.runtimes.get( From 302d5dc82f1c3e2240e2557325bca05eaac4881c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:09:50 +0000 Subject: [PATCH 27/27] release: 0.12.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 41 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/rizaio/_version.py | 2 +- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f7014c35..a7130553 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.11.0" + ".": "0.12.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 8286924e..ec895513 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,46 @@ # Changelog +## 0.12.0 (2025-06-29) + +Full Changelog: [v0.11.0...v0.12.0](https://github.com/riza-io/riza-api-python/compare/v0.11.0...v0.12.0) + +### Features + +* **api:** api update ([345e02a](https://github.com/riza-io/riza-api-python/commit/345e02a9e93c2ca9a26ce0f884dc9a018cd495cd)) +* **api:** api update ([54b8081](https://github.com/riza-io/riza-api-python/commit/54b808196ab410711a60e1e6eafc809045255580)) +* **api:** api update ([ab3a1f5](https://github.com/riza-io/riza-api-python/commit/ab3a1f5a2597a2407a96124c5bbd620eb61aed1a)) +* **api:** api update ([333d168](https://github.com/riza-io/riza-api-python/commit/333d1682a5d6f11000efba046a7874a156a6b4da)) +* **client:** add follow_redirects request option ([729c66e](https://github.com/riza-io/riza-api-python/commit/729c66e844308a7a56c4a7c56ce5af42f4abed0d)) +* **client:** add support for aiohttp ([b433926](https://github.com/riza-io/riza-api-python/commit/b433926d78e66e1234125f155aa6f2458be768ea)) + + +### Bug Fixes + +* **client:** correctly parse binary response | stream ([2b6e46f](https://github.com/riza-io/riza-api-python/commit/2b6e46ffc653e27831681d2a6e3719f6f2e79d4d)) +* **package:** support direct resource imports ([5a8f2bd](https://github.com/riza-io/riza-api-python/commit/5a8f2bd588ed788643b59c1f64bf88af21f8774e)) +* **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([4ebdf17](https://github.com/riza-io/riza-api-python/commit/4ebdf17f9eba4d440261e307da12be148e4ab8c8)) + + +### Chores + +* **ci:** enable for pull requests ([ce5b546](https://github.com/riza-io/riza-api-python/commit/ce5b546ced715900bff3f6d5a965ff482c20d46c)) +* **ci:** fix installation instructions ([dbfcc81](https://github.com/riza-io/riza-api-python/commit/dbfcc815e5022d343677c8eae511024949838c37)) +* **ci:** upload sdks to package manager ([6f15584](https://github.com/riza-io/riza-api-python/commit/6f15584c25e495105f80fe0728b693335a431355)) +* **docs:** grammar improvements ([8254ef9](https://github.com/riza-io/riza-api-python/commit/8254ef91bec882adfb05e4d86dfc13c0b0abcaca)) +* **docs:** remove reference to rye shell ([e7c147b](https://github.com/riza-io/riza-api-python/commit/e7c147beb4fc7f74c0e883389c53e2490863a8e2)) +* **docs:** remove unnecessary param examples ([5dc8d89](https://github.com/riza-io/riza-api-python/commit/5dc8d89c62b934da26ef8a588a26452e64aec895)) +* **internal:** avoid errors for isinstance checks on proxies ([df06fdf](https://github.com/riza-io/riza-api-python/commit/df06fdf1fd3534a47862a98e9df3868dc8813dd9)) +* **internal:** update conftest.py ([bd8931e](https://github.com/riza-io/riza-api-python/commit/bd8931e52ca3ae4591b692aafeded90dfc1d20fd)) +* **readme:** update badges ([9399af4](https://github.com/riza-io/riza-api-python/commit/9399af448b09b28392d91f980fbf2ef5f54b2364)) +* **tests:** add tests for httpx client instantiation & proxies ([81ec20b](https://github.com/riza-io/riza-api-python/commit/81ec20b4c46eacb48f8f715aa195d621e2e663de)) +* **tests:** run tests in parallel ([9c06af9](https://github.com/riza-io/riza-api-python/commit/9c06af9e919b3037638e51813d6172d6f5e9318e)) +* **tests:** skip some failing tests on the latest python versions ([afe509f](https://github.com/riza-io/riza-api-python/commit/afe509f0aad8d8e1f75a4a55af506f7ec178005a)) + + +### Documentation + +* **client:** fix httpx.Timeout documentation reference ([75b4f8d](https://github.com/riza-io/riza-api-python/commit/75b4f8d02e06917aa76e2321b65425aa59c7bf57)) + ## 0.11.0 (2025-04-24) Full Changelog: [v0.10.0...v0.11.0](https://github.com/riza-io/riza-api-python/compare/v0.10.0...v0.11.0) diff --git a/pyproject.toml b/pyproject.toml index 146edc35..529e45ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "rizaio" -version = "0.11.0" +version = "0.12.0" description = "The official Python library for the riza API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/rizaio/_version.py b/src/rizaio/_version.py index c9c8c0e0..02bdd107 100644 --- a/src/rizaio/_version.py +++ b/src/rizaio/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "rizaio" -__version__ = "0.11.0" # x-release-please-version +__version__ = "0.12.0" # x-release-please-version