diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6db19b95..4ad3fef3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.17.0" + ".": "0.18.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 8cf9ee75..9e4dbe62 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 65 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e914e2d08b888c77051acb09176d5e88052f130e0d22e85d946a675d2c3d39ab.yml -openapi_spec_hash: 611d0ed1b4519331470b5d14e5f6784a -config_hash: 3ded7a0ed77b1bfd68eabc6763521fe8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-015c11efc34c81d4d82a937c017f5eb789ea3ca21a05b70e2ed31c069b839293.yml +openapi_spec_hash: 3dcab2044da305f376cecf4eca38caee +config_hash: 0fbdda3a736cc2748ca33371871e61b3 diff --git a/CHANGELOG.md b/CHANGELOG.md index 204a698b..81dc4886 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 0.18.0 (2025-10-30) + +Full Changelog: [v0.17.0...v0.18.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.17.0...v0.18.0) + +### Features + +* apps: add offset pagination + headers ([54ea6e7](https://github.com/onkernel/kernel-python-sdk/commit/54ea6e79ba21ba5ab33ab14a1bf257a95bf76bb8)) + + +### Bug Fixes + +* **client:** close streams without requiring full consumption ([4014c3d](https://github.com/onkernel/kernel-python-sdk/commit/4014c3d53c36627ecd702b8af272a382529aef55)) + ## 0.17.0 (2025-10-27) Full Changelog: [v0.16.0...v0.17.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.16.0...v0.17.0) diff --git a/api.md b/api.md index 164a8c47..aa2ef0e8 100644 --- a/api.md +++ b/api.md @@ -35,7 +35,7 @@ from kernel.types import AppListResponse Methods: -- client.apps.list(\*\*params) -> AppListResponse +- client.apps.list(\*\*params) -> SyncOffsetPagination[AppListResponse] # Invocations diff --git a/pyproject.toml b/pyproject.toml index 1a56293c..737af0ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "kernel" -version = "0.17.0" +version = "0.18.0" description = "The official Python library for the kernel API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/kernel/_streaming.py b/src/kernel/_streaming.py index e3131a3b..e6d03062 100644 --- a/src/kernel/_streaming.py +++ b/src/kernel/_streaming.py @@ -57,9 +57,8 @@ def __stream__(self) -> Iterator[_T]: for sse in iterator: yield process_data(data=sse.json(), cast_to=cast_to, response=response) - # Ensure the entire stream is consumed - for _sse in iterator: - ... + # As we might not fully consume the response stream, we need to close it explicitly + response.close() def __enter__(self) -> Self: return self @@ -121,9 +120,8 @@ async def __stream__(self) -> AsyncIterator[_T]: async for sse in iterator: yield process_data(data=sse.json(), cast_to=cast_to, response=response) - # Ensure the entire stream is consumed - async for _sse in iterator: - ... + # As we might not fully consume the response stream, we need to close it explicitly + await response.aclose() async def __aenter__(self) -> Self: return self diff --git a/src/kernel/_version.py b/src/kernel/_version.py index 123dd308..abb53bdb 100644 --- a/src/kernel/_version.py +++ b/src/kernel/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "kernel" -__version__ = "0.17.0" # x-release-please-version +__version__ = "0.18.0" # x-release-please-version diff --git a/src/kernel/resources/apps.py b/src/kernel/resources/apps.py index 28117b98..34aa1299 100644 --- a/src/kernel/resources/apps.py +++ b/src/kernel/resources/apps.py @@ -6,7 +6,7 @@ from ..types import app_list_params from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given -from .._utils import maybe_transform, async_maybe_transform +from .._utils import maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -15,7 +15,8 @@ async_to_raw_response_wrapper, async_to_streamed_response_wrapper, ) -from .._base_client import make_request_options +from ..pagination import SyncOffsetPagination, AsyncOffsetPagination +from .._base_client import AsyncPaginator, make_request_options from ..types.app_list_response import AppListResponse __all__ = ["AppsResource", "AsyncAppsResource"] @@ -45,6 +46,8 @@ def list( self, *, app_name: str | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, version: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -52,7 +55,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AppListResponse: + ) -> SyncOffsetPagination[AppListResponse]: """List applications. Optionally filter by app name and/or version label. @@ -60,6 +63,10 @@ def list( Args: app_name: Filter results by application name. + limit: Limit the number of app to return. + + offset: Offset the number of app to return. + version: Filter results by version label. extra_headers: Send extra headers @@ -70,8 +77,9 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - return self._get( + return self._get_api_list( "/apps", + page=SyncOffsetPagination[AppListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -80,12 +88,14 @@ def list( query=maybe_transform( { "app_name": app_name, + "limit": limit, + "offset": offset, "version": version, }, app_list_params.AppListParams, ), ), - cast_to=AppListResponse, + model=AppListResponse, ) @@ -109,10 +119,12 @@ def with_streaming_response(self) -> AsyncAppsResourceWithStreamingResponse: """ return AsyncAppsResourceWithStreamingResponse(self) - async def list( + def list( self, *, app_name: str | Omit = omit, + limit: int | Omit = omit, + offset: int | Omit = omit, version: str | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -120,7 +132,7 @@ async def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AppListResponse: + ) -> AsyncPaginator[AppListResponse, AsyncOffsetPagination[AppListResponse]]: """List applications. Optionally filter by app name and/or version label. @@ -128,6 +140,10 @@ async def list( Args: app_name: Filter results by application name. + limit: Limit the number of app to return. + + offset: Offset the number of app to return. + version: Filter results by version label. extra_headers: Send extra headers @@ -138,22 +154,25 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._get( + return self._get_api_list( "/apps", + page=AsyncOffsetPagination[AppListResponse], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout, - query=await async_maybe_transform( + query=maybe_transform( { "app_name": app_name, + "limit": limit, + "offset": offset, "version": version, }, app_list_params.AppListParams, ), ), - cast_to=AppListResponse, + model=AppListResponse, ) diff --git a/src/kernel/types/app_list_params.py b/src/kernel/types/app_list_params.py index d4506a3e..1e2f5278 100644 --- a/src/kernel/types/app_list_params.py +++ b/src/kernel/types/app_list_params.py @@ -11,5 +11,11 @@ class AppListParams(TypedDict, total=False): app_name: str """Filter results by application name.""" + limit: int + """Limit the number of app to return.""" + + offset: int + """Offset the number of app to return.""" + version: str """Filter results by version label.""" diff --git a/src/kernel/types/app_list_response.py b/src/kernel/types/app_list_response.py index bdbc3e61..56a2d4bd 100644 --- a/src/kernel/types/app_list_response.py +++ b/src/kernel/types/app_list_response.py @@ -1,15 +1,15 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List -from typing_extensions import Literal, TypeAlias +from typing_extensions import Literal from .._models import BaseModel from .shared.app_action import AppAction -__all__ = ["AppListResponse", "AppListResponseItem"] +__all__ = ["AppListResponse"] -class AppListResponseItem(BaseModel): +class AppListResponse(BaseModel): id: str """Unique identifier for the app version""" @@ -30,6 +30,3 @@ class AppListResponseItem(BaseModel): version: str """Version label for the application""" - - -AppListResponse: TypeAlias = List[AppListResponseItem] diff --git a/tests/api_resources/test_apps.py b/tests/api_resources/test_apps.py index 5e6db3ba..7475bcd5 100644 --- a/tests/api_resources/test_apps.py +++ b/tests/api_resources/test_apps.py @@ -10,6 +10,7 @@ from kernel import Kernel, AsyncKernel from tests.utils import assert_matches_type from kernel.types import AppListResponse +from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,16 +22,18 @@ class TestApps: @parametrize def test_method_list(self, client: Kernel) -> None: app = client.apps.list() - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(SyncOffsetPagination[AppListResponse], app, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize def test_method_list_with_all_params(self, client: Kernel) -> None: app = client.apps.list( app_name="app_name", + limit=1, + offset=0, version="version", ) - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(SyncOffsetPagination[AppListResponse], app, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -40,7 +43,7 @@ def test_raw_response_list(self, client: Kernel) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" app = response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(SyncOffsetPagination[AppListResponse], app, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -50,7 +53,7 @@ def test_streaming_response_list(self, client: Kernel) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" app = response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(SyncOffsetPagination[AppListResponse], app, path=["response"]) assert cast(Any, response.is_closed) is True @@ -64,16 +67,18 @@ class TestAsyncApps: @parametrize async def test_method_list(self, async_client: AsyncKernel) -> None: app = await async_client.apps.list() - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AppListResponse], app, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None: app = await async_client.apps.list( app_name="app_name", + limit=1, + offset=0, version="version", ) - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AppListResponse], app, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -83,7 +88,7 @@ async def test_raw_response_list(self, async_client: AsyncKernel) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" app = await response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AppListResponse], app, path=["response"]) @pytest.mark.skip(reason="Prism tests are disabled") @parametrize @@ -93,6 +98,6 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" app = await response.parse() - assert_matches_type(AppListResponse, app, path=["response"]) + assert_matches_type(AsyncOffsetPagination[AppListResponse], app, path=["response"]) assert cast(Any, response.is_closed) is True