Skip to content

Commit e5838f5

Browse files
feat(api): add pagination to the deployments endpoint
1 parent 4818d2d commit e5838f5

File tree

8 files changed

+239
-39
lines changed

8 files changed

+239
-39
lines changed

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 46
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-e98d46c55826cdf541a9ee0df04ce92806ac6d4d92957ae79f897270b7d85b23.yml
3-
openapi_spec_hash: 8a1af54fc0a4417165b8a52e6354b685
4-
config_hash: 043ddc54629c6d8b889123770cb4769f
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fkernel-33f1feaba7bde46bfa36d2fefb5c3bc9512967945bccf78045ad3f64aafc4eb0.yml
3+
openapi_spec_hash: 52a448889d41216d1ca30e8a57115b14
4+
config_hash: 1f28d5c3c063f418ebd2799df1e4e781

README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,79 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ
115115

116116
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`.
117117

118+
## Pagination
119+
120+
List methods in the Kernel API are paginated.
121+
122+
This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:
123+
124+
```python
125+
from kernel import Kernel
126+
127+
client = Kernel()
128+
129+
all_deployments = []
130+
# Automatically fetches more pages as needed.
131+
for deployment in client.deployments.list(
132+
app_name="YOUR_APP",
133+
limit=2,
134+
):
135+
# Do something with deployment here
136+
all_deployments.append(deployment)
137+
print(all_deployments)
138+
```
139+
140+
Or, asynchronously:
141+
142+
```python
143+
import asyncio
144+
from kernel import AsyncKernel
145+
146+
client = AsyncKernel()
147+
148+
149+
async def main() -> None:
150+
all_deployments = []
151+
# Iterate through items across all pages, issuing requests as needed.
152+
async for deployment in client.deployments.list(
153+
app_name="YOUR_APP",
154+
limit=2,
155+
):
156+
all_deployments.append(deployment)
157+
print(all_deployments)
158+
159+
160+
asyncio.run(main())
161+
```
162+
163+
Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:
164+
165+
```python
166+
first_page = await client.deployments.list(
167+
app_name="YOUR_APP",
168+
limit=2,
169+
)
170+
if first_page.has_next_page():
171+
print(f"will fetch next page using these details: {first_page.next_page_info()}")
172+
next_page = await first_page.get_next_page()
173+
print(f"number of items we just fetched: {len(next_page.items)}")
174+
175+
# Remove `await` for non-async usage.
176+
```
177+
178+
Or just work directly with the returned data:
179+
180+
```python
181+
first_page = await client.deployments.list(
182+
app_name="YOUR_APP",
183+
limit=2,
184+
)
185+
for deployment in first_page.items:
186+
print(deployment.id)
187+
188+
# Remove `await` for non-async usage.
189+
```
190+
118191
## Nested params
119192

120193
Nested parameters are dictionaries, typed using `TypedDict`, for example:

api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Methods:
2222

2323
- <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>
2424
- <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>
25-
- <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>
25+
- <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>
2626
- <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>
2727

2828
# Apps

src/kernel/pagination.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
from typing import Any, List, Type, Generic, Mapping, TypeVar, Optional, cast
4+
from typing_extensions import override
5+
6+
from httpx import Response
7+
8+
from ._utils import is_mapping
9+
from ._models import BaseModel
10+
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
11+
12+
__all__ = ["SyncOffsetPagination", "AsyncOffsetPagination"]
13+
14+
_BaseModelT = TypeVar("_BaseModelT", bound=BaseModel)
15+
16+
_T = TypeVar("_T")
17+
18+
19+
class SyncOffsetPagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
20+
items: List[_T]
21+
22+
@override
23+
def _get_page_items(self) -> List[_T]:
24+
items = self.items
25+
if not items:
26+
return []
27+
return items
28+
29+
@override
30+
def next_page_info(self) -> Optional[PageInfo]:
31+
offset = self._options.params.get("offset") or 0
32+
if not isinstance(offset, int):
33+
raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
34+
35+
length = len(self._get_page_items())
36+
current_count = offset + length
37+
38+
return PageInfo(params={"offset": current_count})
39+
40+
@classmethod
41+
def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
42+
return cls.construct(
43+
None,
44+
**{
45+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
46+
},
47+
)
48+
49+
50+
class AsyncOffsetPagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
51+
items: List[_T]
52+
53+
@override
54+
def _get_page_items(self) -> List[_T]:
55+
items = self.items
56+
if not items:
57+
return []
58+
return items
59+
60+
@override
61+
def next_page_info(self) -> Optional[PageInfo]:
62+
offset = self._options.params.get("offset") or 0
63+
if not isinstance(offset, int):
64+
raise ValueError(f'Expected "offset" param to be an integer but got {offset}')
65+
66+
length = len(self._get_page_items())
67+
current_count = offset + length
68+
69+
return PageInfo(params={"offset": current_count})
70+
71+
@classmethod
72+
def build(cls: Type[_BaseModelT], *, response: Response, data: object) -> _BaseModelT: # noqa: ARG003
73+
return cls.construct(
74+
None,
75+
**{
76+
**(cast(Mapping[str, Any], data) if is_mapping(data) else {"items": data}),
77+
},
78+
)

src/kernel/resources/deployments.py

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
async_to_streamed_response_wrapper,
2020
)
2121
from .._streaming import Stream, AsyncStream
22-
from .._base_client import make_request_options
22+
from ..pagination import SyncOffsetPagination, AsyncOffsetPagination
23+
from .._base_client import AsyncPaginator, make_request_options
2324
from ..types.deployment_list_response import DeploymentListResponse
2425
from ..types.deployment_create_response import DeploymentCreateResponse
2526
from ..types.deployment_follow_response import DeploymentFollowResponse
@@ -150,21 +151,27 @@ def retrieve(
150151
def list(
151152
self,
152153
*,
153-
app_name: str | NotGiven = NOT_GIVEN,
154+
app_name: str,
155+
limit: int | NotGiven = NOT_GIVEN,
156+
offset: int | NotGiven = NOT_GIVEN,
154157
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
155158
# The extra values given here take precedence over values defined on the client or passed to this method.
156159
extra_headers: Headers | None = None,
157160
extra_query: Query | None = None,
158161
extra_body: Body | None = None,
159162
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
160-
) -> DeploymentListResponse:
163+
) -> SyncOffsetPagination[DeploymentListResponse]:
161164
"""List deployments.
162165
163166
Optionally filter by application name.
164167
165168
Args:
166169
app_name: Filter results by application name.
167170
171+
limit: Limit the number of deployments to return.
172+
173+
offset: Offset the number of deployments to return.
174+
168175
extra_headers: Send extra headers
169176
170177
extra_query: Add additional query parameters to the request
@@ -173,16 +180,24 @@ def list(
173180
174181
timeout: Override the client-level default timeout for this request, in seconds
175182
"""
176-
return self._get(
183+
return self._get_api_list(
177184
"/deployments",
185+
page=SyncOffsetPagination[DeploymentListResponse],
178186
options=make_request_options(
179187
extra_headers=extra_headers,
180188
extra_query=extra_query,
181189
extra_body=extra_body,
182190
timeout=timeout,
183-
query=maybe_transform({"app_name": app_name}, deployment_list_params.DeploymentListParams),
191+
query=maybe_transform(
192+
{
193+
"app_name": app_name,
194+
"limit": limit,
195+
"offset": offset,
196+
},
197+
deployment_list_params.DeploymentListParams,
198+
),
184199
),
185-
cast_to=DeploymentListResponse,
200+
model=DeploymentListResponse,
186201
)
187202

188203
def follow(
@@ -352,24 +367,30 @@ async def retrieve(
352367
cast_to=DeploymentRetrieveResponse,
353368
)
354369

355-
async def list(
370+
def list(
356371
self,
357372
*,
358-
app_name: str | NotGiven = NOT_GIVEN,
373+
app_name: str,
374+
limit: int | NotGiven = NOT_GIVEN,
375+
offset: int | NotGiven = NOT_GIVEN,
359376
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
360377
# The extra values given here take precedence over values defined on the client or passed to this method.
361378
extra_headers: Headers | None = None,
362379
extra_query: Query | None = None,
363380
extra_body: Body | None = None,
364381
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
365-
) -> DeploymentListResponse:
382+
) -> AsyncPaginator[DeploymentListResponse, AsyncOffsetPagination[DeploymentListResponse]]:
366383
"""List deployments.
367384
368385
Optionally filter by application name.
369386
370387
Args:
371388
app_name: Filter results by application name.
372389
390+
limit: Limit the number of deployments to return.
391+
392+
offset: Offset the number of deployments to return.
393+
373394
extra_headers: Send extra headers
374395
375396
extra_query: Add additional query parameters to the request
@@ -378,16 +399,24 @@ async def list(
378399
379400
timeout: Override the client-level default timeout for this request, in seconds
380401
"""
381-
return await self._get(
402+
return self._get_api_list(
382403
"/deployments",
404+
page=AsyncOffsetPagination[DeploymentListResponse],
383405
options=make_request_options(
384406
extra_headers=extra_headers,
385407
extra_query=extra_query,
386408
extra_body=extra_body,
387409
timeout=timeout,
388-
query=await async_maybe_transform({"app_name": app_name}, deployment_list_params.DeploymentListParams),
410+
query=maybe_transform(
411+
{
412+
"app_name": app_name,
413+
"limit": limit,
414+
"offset": offset,
415+
},
416+
deployment_list_params.DeploymentListParams,
417+
),
389418
),
390-
cast_to=DeploymentListResponse,
419+
model=DeploymentListResponse,
391420
)
392421

393422
async def follow(

src/kernel/types/deployment_list_params.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22

33
from __future__ import annotations
44

5-
from typing_extensions import TypedDict
5+
from typing_extensions import Required, TypedDict
66

77
__all__ = ["DeploymentListParams"]
88

99

1010
class DeploymentListParams(TypedDict, total=False):
11-
app_name: str
11+
app_name: Required[str]
1212
"""Filter results by application name."""
13+
14+
limit: int
15+
"""Limit the number of deployments to return."""
16+
17+
offset: int
18+
"""Offset the number of deployments to return."""

src/kernel/types/deployment_list_response.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3-
from typing import Dict, List, Optional
3+
from typing import Dict, Optional
44
from datetime import datetime
5-
from typing_extensions import Literal, TypeAlias
5+
from typing_extensions import Literal
66

77
from .._models import BaseModel
88

9-
__all__ = ["DeploymentListResponse", "DeploymentListResponseItem"]
9+
__all__ = ["DeploymentListResponse"]
1010

1111

12-
class DeploymentListResponseItem(BaseModel):
12+
class DeploymentListResponse(BaseModel):
1313
id: str
1414
"""Unique identifier for the deployment"""
1515

@@ -33,6 +33,3 @@ class DeploymentListResponseItem(BaseModel):
3333

3434
updated_at: Optional[datetime] = None
3535
"""Timestamp when the deployment was last updated"""
36-
37-
38-
DeploymentListResponse: TypeAlias = List[DeploymentListResponseItem]

0 commit comments

Comments
 (0)