Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.11.0"
".": "0.11.1"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Methods:

- <code title="post /deployments">client.deployments.<a href="./src/kernel/resources/deployments.py">create</a>(\*\*<a href="src/kernel/types/deployment_create_params.py">params</a>) -> <a href="./src/kernel/types/deployment_create_response.py">DeploymentCreateResponse</a></code>
- <code title="get /deployments/{id}">client.deployments.<a href="./src/kernel/resources/deployments.py">retrieve</a>(id) -> <a href="./src/kernel/types/deployment_retrieve_response.py">DeploymentRetrieveResponse</a></code>
- <code title="get /deployments">client.deployments.<a href="./src/kernel/resources/deployments.py">list</a>(\*\*<a href="src/kernel/types/deployment_list_params.py">params</a>) -> <a href="./src/kernel/types/deployment_list_response.py">DeploymentListResponse</a></code>
- <code title="get /deployments">client.deployments.<a href="./src/kernel/resources/deployments.py">list</a>(\*\*<a href="src/kernel/types/deployment_list_params.py">params</a>) -> <a href="./src/kernel/types/deployment_list_response.py">SyncOffsetPagination[DeploymentListResponse]</a></code>
- <code title="get /deployments/{id}/events">client.deployments.<a href="./src/kernel/resources/deployments.py">follow</a>(id, \*\*<a href="src/kernel/types/deployment_follow_params.py">params</a>) -> <a href="./src/kernel/types/deployment_follow_response.py">DeploymentFollowResponse</a></code>

# Apps
Expand Down
50 changes: 0 additions & 50 deletions mypy.ini

This file was deleted.

55 changes: 53 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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",
]

Expand Down Expand Up @@ -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/.*']
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium Logic

The exclude pattern format has changed from regex format (^(src/kernel/_files\.py|_dev/.*\.py|tests/.*)$) to a simpler glob-like format (['src/kernel/_files.py', '_dev/.*.py', 'tests/.*']). While this may work, you should verify that mypy correctly interprets this new format, especially for the wildcard patterns like _dev/.*.py and tests/.*. The original regex format was more explicit about matching paths.


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"
Expand Down
1 change: 0 additions & 1 deletion requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/kernel/_version.py
Original file line number Diff line number Diff line change
@@ -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
102 changes: 102 additions & 0 deletions src/kernel/pagination.py
Original file line number Diff line number Diff line change
@@ -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")),
},
)
Loading