diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index f7014c3..e82003f 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.11.0"
+ ".": "0.11.1"
}
\ No newline at end of file
diff --git a/.stats.yml b/.stats.yml
index 6ac19ba..7fb3d31 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 46
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e98d46c55826cdf541a9ee0df04ce92806ac6d4d92957ae79f897270b7d85b23.yml
-openapi_spec_hash: 8a1af54fc0a4417165b8a52e6354b685
-config_hash: 043ddc54629c6d8b889123770cb4769f
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-cb38560915edce03abce2ae3ef5bc745489dbe9b6f80c2b4ff42edf8c2ff276d.yml
+openapi_spec_hash: a869194d6c864ba28d79ec0105439c3e
+config_hash: ed56f95781ec9b2e73c97e1a66606071
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6a6cec..5d1ad48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,21 @@
# Changelog
+## 0.11.1 (2025-09-06)
+
+Full Changelog: [v0.11.0...v0.11.1](https://github.com/onkernel/kernel-python-sdk/compare/v0.11.0...v0.11.1)
+
+### Features
+
+* **api:** add pagination to the deployments endpoint ([e5838f5](https://github.com/onkernel/kernel-python-sdk/commit/e5838f51b9af325700b23d55ff2bb11b6ff3306e))
+* **api:** pagination properties added to response (has_more, next_offset) ([5f2329f](https://github.com/onkernel/kernel-python-sdk/commit/5f2329f8712b9d1865cc95dcde06834fe65622ee))
+* **api:** update API spec with pagination headers ([f64f55b](https://github.com/onkernel/kernel-python-sdk/commit/f64f55b00b0e0fa19dd2162cd914001381254314))
+
+
+### Chores
+
+* **internal:** move mypy configurations to `pyproject.toml` file ([4818d2d](https://github.com/onkernel/kernel-python-sdk/commit/4818d2d6084684529935c8e6b9b109516a1de373))
+* **tests:** simplify `get_platform` test ([cd90a49](https://github.com/onkernel/kernel-python-sdk/commit/cd90a498d24b1f4490583bec64e5e670eb725197))
+
## 0.11.0 (2025-09-04)
Full Changelog: [v0.10.0...v0.11.0](https://github.com/onkernel/kernel-python-sdk/compare/v0.10.0...v0.11.0)
diff --git a/README.md b/README.md
index 884d10c..beec3f0 100644
--- a/README.md
+++ b/README.md
@@ -115,6 +115,83 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.
+## Pagination
+
+List methods in the Kernel API are paginated.
+
+This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
+
+```python
+from kernel import Kernel
+
+client = Kernel()
+
+all_deployments = []
+# Automatically fetches more pages as needed.
+for deployment in client.deployments.list(
+ app_name="YOUR_APP",
+ limit=2,
+):
+ # Do something with deployment here
+ all_deployments.append(deployment)
+print(all_deployments)
+```
+
+Or, asynchronously:
+
+```python
+import asyncio
+from kernel import AsyncKernel
+
+client = AsyncKernel()
+
+
+async def main() -> None:
+ all_deployments = []
+ # Iterate through items across all pages, issuing requests as needed.
+ async for deployment in client.deployments.list(
+ app_name="YOUR_APP",
+ limit=2,
+ ):
+ all_deployments.append(deployment)
+ print(all_deployments)
+
+
+asyncio.run(main())
+```
+
+Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
+
+```python
+first_page = await client.deployments.list(
+ app_name="YOUR_APP",
+ limit=2,
+)
+if first_page.has_next_page():
+ print(f"will fetch next page using these details: {first_page.next_page_info()}")
+ next_page = await first_page.get_next_page()
+ print(f"number of items we just fetched: {len(next_page.items)}")
+
+# Remove `await` for non-async usage.
+```
+
+Or just work directly with the returned data:
+
+```python
+first_page = await client.deployments.list(
+ app_name="YOUR_APP",
+ limit=2,
+)
+
+print(
+ f"the current start offset for this page: {first_page.next_offset}"
+) # => "the current start offset for this page: 1"
+for deployment in first_page.items:
+ print(deployment.id)
+
+# Remove `await` for non-async usage.
+```
+
## Nested params
Nested parameters are dictionaries, typed using `TypedDict`, for example:
diff --git a/api.md b/api.md
index 7829585..56d852b 100644
--- a/api.md
+++ b/api.md
@@ -22,7 +22,7 @@ Methods:
- client.deployments.create(\*\*params) -> DeploymentCreateResponse
- client.deployments.retrieve(id) -> DeploymentRetrieveResponse
-- client.deployments.list(\*\*params) -> DeploymentListResponse
+- client.deployments.list(\*\*params) -> SyncOffsetPagination[DeploymentListResponse]
- client.deployments.follow(id, \*\*params) -> DeploymentFollowResponse
# Apps
diff --git a/mypy.ini b/mypy.ini
deleted file mode 100644
index 0745431..0000000
--- a/mypy.ini
+++ /dev/null
@@ -1,50 +0,0 @@
-[mypy]
-pretty = True
-show_error_codes = True
-
-# Exclude _files.py because mypy isn't smart enough to apply
-# the correct type narrowing and as this is an internal module
-# it's fine to just use Pyright.
-#
-# We also exclude our `tests` as mypy doesn't always infer
-# types correctly and Pyright will still catch any type errors.
-exclude = ^(src/kernel/_files\.py|_dev/.*\.py|tests/.*)$
-
-strict_equality = True
-implicit_reexport = True
-check_untyped_defs = True
-no_implicit_optional = True
-
-warn_return_any = True
-warn_unreachable = True
-warn_unused_configs = True
-
-# Turn these options off as it could cause conflicts
-# with the Pyright options.
-warn_unused_ignores = False
-warn_redundant_casts = False
-
-disallow_any_generics = True
-disallow_untyped_defs = True
-disallow_untyped_calls = True
-disallow_subclassing_any = True
-disallow_incomplete_defs = True
-disallow_untyped_decorators = True
-cache_fine_grained = True
-
-# By default, mypy reports an error if you assign a value to the result
-# of a function call that doesn't return anything. We do this in our test
-# cases:
-# ```
-# result = ...
-# assert result is None
-# ```
-# Changing this codegen to make mypy happy would increase complexity
-# and would not be worth it.
-disable_error_code = func-returns-value,overload-cannot-match
-
-# https://github.com/python/mypy/issues/12162
-[mypy.overrides]
-module = "black.files.*"
-ignore_errors = true
-ignore_missing_imports = true
diff --git a/pyproject.toml b/pyproject.toml
index f731ab3..5533264 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "kernel"
-version = "0.11.0"
+version = "0.11.1"
description = "The official Python library for the kernel API"
dynamic = ["readme"]
license = "Apache-2.0"
@@ -56,7 +56,6 @@ dev-dependencies = [
"dirty-equals>=0.6.0",
"importlib-metadata>=6.7.0",
"rich>=13.7.1",
- "nest_asyncio==1.6.0",
"pytest-xdist>=3.6.1",
]
@@ -157,6 +156,58 @@ reportOverlappingOverload = false
reportImportCycles = false
reportPrivateUsage = false
+[tool.mypy]
+pretty = true
+show_error_codes = true
+
+# Exclude _files.py because mypy isn't smart enough to apply
+# the correct type narrowing and as this is an internal module
+# it's fine to just use Pyright.
+#
+# We also exclude our `tests` as mypy doesn't always infer
+# types correctly and Pyright will still catch any type errors.
+exclude = ['src/kernel/_files.py', '_dev/.*.py', 'tests/.*']
+
+strict_equality = true
+implicit_reexport = true
+check_untyped_defs = true
+no_implicit_optional = true
+
+warn_return_any = true
+warn_unreachable = true
+warn_unused_configs = true
+
+# Turn these options off as it could cause conflicts
+# with the Pyright options.
+warn_unused_ignores = false
+warn_redundant_casts = false
+
+disallow_any_generics = true
+disallow_untyped_defs = true
+disallow_untyped_calls = true
+disallow_subclassing_any = true
+disallow_incomplete_defs = true
+disallow_untyped_decorators = true
+cache_fine_grained = true
+
+# By default, mypy reports an error if you assign a value to the result
+# of a function call that doesn't return anything. We do this in our test
+# cases:
+# ```
+# result = ...
+# assert result is None
+# ```
+# Changing this codegen to make mypy happy would increase complexity
+# and would not be worth it.
+disable_error_code = "func-returns-value,overload-cannot-match"
+
+# https://github.com/python/mypy/issues/12162
+[[tool.mypy.overrides]]
+module = "black.files.*"
+ignore_errors = true
+ignore_missing_imports = true
+
+
[tool.ruff]
line-length = 120
output-format = "grouped"
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 55681a9..56d0acc 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -75,7 +75,6 @@ multidict==6.4.4
mypy==1.14.1
mypy-extensions==1.0.0
# via mypy
-nest-asyncio==1.6.0
nodeenv==1.8.0
# via pyright
nox==2023.4.22
diff --git a/src/kernel/_version.py b/src/kernel/_version.py
index 1caec76..0e02001 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.11.0" # x-release-please-version
+__version__ = "0.11.1" # x-release-please-version
diff --git a/src/kernel/pagination.py b/src/kernel/pagination.py
new file mode 100644
index 0000000..cdf83c2
--- /dev/null
+++ b/src/kernel/pagination.py
@@ -0,0 +1,102 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
+from typing_extensions import override
+
+from httpx import Response
+
+from ._utils import is_mapping, maybe_coerce_boolean, maybe_coerce_integer
+from ._models import BaseModel
+from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
+
+__all__ = ["SyncOffsetPagination", "AsyncOffsetPagination"]
+
+_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
+
+_T = TypeVar("_T")
+
+
+class SyncOffsetPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+ items: List[_T]
+ has_more: Optional[bool] = None
+ next_offset: Optional[int] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ items = self.items
+ if not items:
+ return []
+ return items
+
+ @override
+ def has_next_page(self) -> bool:
+ has_more = self.has_more
+ if has_more is not None and has_more is False:
+ return False
+
+ return super().has_next_page()
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next_offset = self.next_offset
+ if next_offset is None:
+ return None # type: ignore[unreachable]
+
+ length = len(self._get_page_items())
+ current_count = next_offset + length
+
+ return PageInfo(params={"offset": current_count})
+
+ @classmethod
+ def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
+ return cls.construct(
+ None,
+ **{
+ **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+ "has_more": maybe_coerce_boolean(response.headers.get("X-Has-More")),
+ "next_offset": maybe_coerce_integer(response.headers.get("X-Next-Offset")),
+ },
+ )
+
+
+class AsyncOffsetPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+ items: List[_T]
+ has_more: Optional[bool] = None
+ next_offset: Optional[int] = None
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ items = self.items
+ if not items:
+ return []
+ return items
+
+ @override
+ def has_next_page(self) -> bool:
+ has_more = self.has_more
+ if has_more is not None and has_more is False:
+ return False
+
+ return super().has_next_page()
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ next_offset = self.next_offset
+ if next_offset is None:
+ return None # type: ignore[unreachable]
+
+ length = len(self._get_page_items())
+ current_count = next_offset + length
+
+ return PageInfo(params={"offset": current_count})
+
+ @classmethod
+ def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
+ return cls.construct(
+ None,
+ **{
+ **(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
+ "has_more": maybe_coerce_boolean(response.headers.get("X-Has-More")),
+ "next_offset": maybe_coerce_integer(response.headers.get("X-Next-Offset")),
+ },
+ )
diff --git a/src/kernel/resources/deployments.py b/src/kernel/resources/deployments.py
index d54c4ec..5c7715d 100644
--- a/src/kernel/resources/deployments.py
+++ b/src/kernel/resources/deployments.py
@@ -19,7 +19,8 @@
async_to_streamed_response_wrapper,
)
from .._streaming import Stream, AsyncStream
-from .._base_client import make_request_options
+from ..pagination import SyncOffsetPagination, AsyncOffsetPagination
+from .._base_client import AsyncPaginator, make_request_options
from ..types.deployment_list_response import DeploymentListResponse
from ..types.deployment_create_response import DeploymentCreateResponse
from ..types.deployment_follow_response import DeploymentFollowResponse
@@ -151,13 +152,15 @@ def list(
self,
*,
app_name: str | NotGiven = NOT_GIVEN,
+ limit: int | NotGiven = NOT_GIVEN,
+ offset: int | 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,
- ) -> DeploymentListResponse:
+ ) -> SyncOffsetPagination[DeploymentListResponse]:
"""List deployments.
Optionally filter by application name.
@@ -165,6 +168,10 @@ def list(
Args:
app_name: Filter results by application name.
+ limit: Limit the number of deployments to return.
+
+ offset: Offset the number of deployments to return.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -173,16 +180,24 @@ def list(
timeout: Override the client-level default timeout for this request, in seconds
"""
- return self._get(
+ return self._get_api_list(
"/deployments",
+ page=SyncOffsetPagination[DeploymentListResponse],
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
- query=maybe_transform({"app_name": app_name}, deployment_list_params.DeploymentListParams),
+ query=maybe_transform(
+ {
+ "app_name": app_name,
+ "limit": limit,
+ "offset": offset,
+ },
+ deployment_list_params.DeploymentListParams,
+ ),
),
- cast_to=DeploymentListResponse,
+ model=DeploymentListResponse,
)
def follow(
@@ -352,17 +367,19 @@ async def retrieve(
cast_to=DeploymentRetrieveResponse,
)
- async def list(
+ def list(
self,
*,
app_name: str | NotGiven = NOT_GIVEN,
+ limit: int | NotGiven = NOT_GIVEN,
+ offset: int | 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,
- ) -> DeploymentListResponse:
+ ) -> AsyncPaginator[DeploymentListResponse, AsyncOffsetPagination[DeploymentListResponse]]:
"""List deployments.
Optionally filter by application name.
@@ -370,6 +387,10 @@ async def list(
Args:
app_name: Filter results by application name.
+ limit: Limit the number of deployments to return.
+
+ offset: Offset the number of deployments to return.
+
extra_headers: Send extra headers
extra_query: Add additional query parameters to the request
@@ -378,16 +399,24 @@ async def list(
timeout: Override the client-level default timeout for this request, in seconds
"""
- return await self._get(
+ return self._get_api_list(
"/deployments",
+ page=AsyncOffsetPagination[DeploymentListResponse],
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
- query=await async_maybe_transform({"app_name": app_name}, deployment_list_params.DeploymentListParams),
+ query=maybe_transform(
+ {
+ "app_name": app_name,
+ "limit": limit,
+ "offset": offset,
+ },
+ deployment_list_params.DeploymentListParams,
+ ),
),
- cast_to=DeploymentListResponse,
+ model=DeploymentListResponse,
)
async def follow(
diff --git a/src/kernel/types/deployment_list_params.py b/src/kernel/types/deployment_list_params.py
index 05704a1..54124da 100644
--- a/src/kernel/types/deployment_list_params.py
+++ b/src/kernel/types/deployment_list_params.py
@@ -10,3 +10,9 @@
class DeploymentListParams(TypedDict, total=False):
app_name: str
"""Filter results by application name."""
+
+ limit: int
+ """Limit the number of deployments to return."""
+
+ offset: int
+ """Offset the number of deployments to return."""
diff --git a/src/kernel/types/deployment_list_response.py b/src/kernel/types/deployment_list_response.py
index ba7759d..d22b007 100644
--- a/src/kernel/types/deployment_list_response.py
+++ b/src/kernel/types/deployment_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, Optional
+from typing import Dict, Optional
from datetime import datetime
-from typing_extensions import Literal, TypeAlias
+from typing_extensions import Literal
from .._models import BaseModel
-__all__ = ["DeploymentListResponse", "DeploymentListResponseItem"]
+__all__ = ["DeploymentListResponse"]
-class DeploymentListResponseItem(BaseModel):
+class DeploymentListResponse(BaseModel):
id: str
"""Unique identifier for the deployment"""
@@ -33,6 +33,3 @@ class DeploymentListResponseItem(BaseModel):
updated_at: Optional[datetime] = None
"""Timestamp when the deployment was last updated"""
-
-
-DeploymentListResponse: TypeAlias = List[DeploymentListResponseItem]
diff --git a/tests/api_resources/test_deployments.py b/tests/api_resources/test_deployments.py
index c177978..fc5d299 100644
--- a/tests/api_resources/test_deployments.py
+++ b/tests/api_resources/test_deployments.py
@@ -14,6 +14,7 @@
DeploymentCreateResponse,
DeploymentRetrieveResponse,
)
+from kernel.pagination import SyncOffsetPagination, AsyncOffsetPagination
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -117,15 +118,17 @@ def test_path_params_retrieve(self, client: Kernel) -> None:
@parametrize
def test_method_list(self, client: Kernel) -> None:
deployment = client.deployments.list()
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(SyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
def test_method_list_with_all_params(self, client: Kernel) -> None:
deployment = client.deployments.list(
app_name="app_name",
+ limit=1,
+ offset=0,
)
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(SyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -135,7 +138,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"
deployment = response.parse()
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(SyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -145,7 +148,7 @@ def test_streaming_response_list(self, client: Kernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
deployment = response.parse()
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(SyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
assert cast(Any, response.is_closed) is True
@@ -301,15 +304,17 @@ async def test_path_params_retrieve(self, async_client: AsyncKernel) -> None:
@parametrize
async def test_method_list(self, async_client: AsyncKernel) -> None:
deployment = await async_client.deployments.list()
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
async def test_method_list_with_all_params(self, async_client: AsyncKernel) -> None:
deployment = await async_client.deployments.list(
app_name="app_name",
+ limit=1,
+ offset=0,
)
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -319,7 +324,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"
deployment = await response.parse()
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
@pytest.mark.skip(reason="Prism tests are disabled")
@parametrize
@@ -329,7 +334,7 @@ async def test_streaming_response_list(self, async_client: AsyncKernel) -> None:
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
deployment = await response.parse()
- assert_matches_type(DeploymentListResponse, deployment, path=["response"])
+ assert_matches_type(AsyncOffsetPagination[DeploymentListResponse], deployment, path=["response"])
assert cast(Any, response.is_closed) is True
diff --git a/tests/test_client.py b/tests/test_client.py
index d5a1784..74ffb05 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -6,13 +6,10 @@
import os
import sys
import json
-import time
import asyncio
import inspect
-import subprocess
import tracemalloc
from typing import Any, Union, cast
-from textwrap import dedent
from unittest import mock
from typing_extensions import Literal
@@ -23,14 +20,17 @@
from kernel import Kernel, AsyncKernel, APIResponseValidationError
from kernel._types import Omit
+from kernel._utils import asyncify
from kernel._models import BaseModel, FinalRequestOptions
from kernel._exceptions import KernelError, APIStatusError, APITimeoutError, APIResponseValidationError
from kernel._base_client import (
DEFAULT_TIMEOUT,
HTTPX_DEFAULT_TIMEOUT,
BaseClient,
+ OtherPlatform,
DefaultHttpxClient,
DefaultAsyncHttpxClient,
+ get_platform,
make_request_options,
)
@@ -1645,50 +1645,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response:
assert response.http_request.headers.get("x-stainless-retry-count") == "42"
- def test_get_platform(self) -> None:
- # A previous implementation of asyncify could leave threads unterminated when
- # used with nest_asyncio.
- #
- # Since nest_asyncio.apply() is global and cannot be un-applied, this
- # test is run in a separate process to avoid affecting other tests.
- test_code = dedent("""
- import asyncio
- import nest_asyncio
- import threading
-
- from kernel._utils import asyncify
- from kernel._base_client import get_platform
-
- async def test_main() -> None:
- result = await asyncify(get_platform)()
- print(result)
- for thread in threading.enumerate():
- print(thread.name)
-
- nest_asyncio.apply()
- asyncio.run(test_main())
- """)
- with subprocess.Popen(
- [sys.executable, "-c", test_code],
- text=True,
- ) as process:
- timeout = 10 # seconds
-
- start_time = time.monotonic()
- while True:
- return_code = process.poll()
- if return_code is not None:
- if return_code != 0:
- raise AssertionError("calling get_platform using asyncify resulted in a non-zero exit code")
-
- # success
- break
-
- if time.monotonic() - start_time > timeout:
- process.kill()
- raise AssertionError("calling get_platform using asyncify resulted in a hung process")
-
- time.sleep(0.1)
+ async def test_get_platform(self) -> None:
+ platform = await asyncify(get_platform)()
+ assert isinstance(platform, (str, OtherPlatform))
async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None:
# Test that the proxy environment variables are set correctly