Skip to content

Commit 177f39a

Browse files
chore(axon): add axon auto-pagination to stainless sdks (#8420)
1 parent 9dd4b43 commit 177f39a

5 files changed

Lines changed: 98 additions & 21 deletions

File tree

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 124
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runloop-ai%2Frunloop-f32de1991bef7651f9b0970b503e2986c7a88708c4a408d54abe6d089795e9f9.yml
33
openapi_spec_hash: d2005d48f75fba4a5368209cf49641dc
4-
config_hash: a759c23a5a04ad26f8740acc7e094c01
4+
config_hash: 5ffcc19219880e7d9ce43c1e6860568c

api.md

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

108108
- <code title="post /v1/axons">client.axons.<a href="./src/runloop_api_client/resources/axons/axons.py">create</a>(\*\*<a href="src/runloop_api_client/types/axon_create_params.py">params</a>) -> <a href="./src/runloop_api_client/types/axon_view.py">AxonView</a></code>
109109
- <code title="get /v1/axons/{id}">client.axons.<a href="./src/runloop_api_client/resources/axons/axons.py">retrieve</a>(id) -> <a href="./src/runloop_api_client/types/axon_view.py">AxonView</a></code>
110-
- <code title="get /v1/axons">client.axons.<a href="./src/runloop_api_client/resources/axons/axons.py">list</a>(\*\*<a href="src/runloop_api_client/types/axon_list_params.py">params</a>) -> <a href="./src/runloop_api_client/types/axon_list_view.py">AxonListView</a></code>
110+
- <code title="get /v1/axons">client.axons.<a href="./src/runloop_api_client/resources/axons/axons.py">list</a>(\*\*<a href="src/runloop_api_client/types/axon_list_params.py">params</a>) -> <a href="./src/runloop_api_client/types/axon_view.py">SyncAxonsCursorIDPage[AxonView]</a></code>
111111
- <code title="post /v1/axons/{id}/publish">client.axons.<a href="./src/runloop_api_client/resources/axons/axons.py">publish</a>(id, \*\*<a href="src/runloop_api_client/types/axon_publish_params.py">params</a>) -> <a href="./src/runloop_api_client/types/publish_result_view.py">PublishResultView</a></code>
112112
- <code title="get /v1/axons/{id}/subscribe/sse">client.axons.<a href="./src/runloop_api_client/resources/axons/axons.py">subscribe_sse</a>(id) -> <a href="./src/runloop_api_client/types/axon_event_view.py">AxonEventView</a></code>
113113

src/runloop_api_client/pagination.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
"AsyncBenchmarksCursorIDPage",
1919
"SyncAgentsCursorIDPage",
2020
"AsyncAgentsCursorIDPage",
21+
"SyncAxonsCursorIDPage",
22+
"AsyncAxonsCursorIDPage",
2123
"SyncBenchmarkRunsCursorIDPage",
2224
"AsyncBenchmarkRunsCursorIDPage",
2325
"SyncScenariosCursorIDPage",
@@ -69,6 +71,11 @@ class AgentsCursorIDPageItem(Protocol):
6971
id: str
7072

7173

74+
@runtime_checkable
75+
class AxonsCursorIDPageItem(Protocol):
76+
id: str
77+
78+
7279
@runtime_checkable
7380
class BenchmarkRunsCursorIDPageItem(Protocol):
7481
id: str
@@ -517,6 +524,74 @@ def next_page_info(self) -> Optional[PageInfo]:
517524
return PageInfo(params={"starting_after": item.id})
518525

519526

527+
class SyncAxonsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
528+
axons: List[_T]
529+
has_more: Optional[bool] = None
530+
total_count: Optional[int] = None
531+
532+
@override
533+
def _get_page_items(self) -> List[_T]:
534+
axons = self.axons
535+
if not axons:
536+
return []
537+
return axons
538+
539+
@override
540+
def has_next_page(self) -> bool:
541+
has_more = self.has_more
542+
if has_more is not None and has_more is False:
543+
return False
544+
545+
return super().has_next_page()
546+
547+
@override
548+
def next_page_info(self) -> Optional[PageInfo]:
549+
axons = self.axons
550+
if not axons:
551+
return None
552+
553+
item = cast(Any, axons[-1])
554+
if not isinstance(item, AxonsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
555+
# TODO emit warning log
556+
return None
557+
558+
return PageInfo(params={"starting_after": item.id})
559+
560+
561+
class AsyncAxonsCursorIDPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
562+
axons: List[_T]
563+
has_more: Optional[bool] = None
564+
total_count: Optional[int] = None
565+
566+
@override
567+
def _get_page_items(self) -> List[_T]:
568+
axons = self.axons
569+
if not axons:
570+
return []
571+
return axons
572+
573+
@override
574+
def has_next_page(self) -> bool:
575+
has_more = self.has_more
576+
if has_more is not None and has_more is False:
577+
return False
578+
579+
return super().has_next_page()
580+
581+
@override
582+
def next_page_info(self) -> Optional[PageInfo]:
583+
axons = self.axons
584+
if not axons:
585+
return None
586+
587+
item = cast(Any, axons[-1])
588+
if not isinstance(item, AxonsCursorIDPageItem) or item.id is None: # pyright: ignore[reportUnnecessaryComparison]
589+
# TODO emit warning log
590+
return None
591+
592+
return PageInfo(params={"starting_after": item.id})
593+
594+
520595
class SyncBenchmarkRunsCursorIDPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
521596
runs: List[_T]
522597
has_more: Optional[bool] = None

src/runloop_api_client/resources/axons/axons.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@
2727
async_to_streamed_response_wrapper,
2828
)
2929
from ..._streaming import Stream, AsyncStream
30-
from ..._base_client import make_request_options
30+
from ...pagination import SyncAxonsCursorIDPage, AsyncAxonsCursorIDPage
31+
from ..._base_client import AsyncPaginator, make_request_options
3132
from ...types.axon_view import AxonView
32-
from ...types.axon_list_view import AxonListView
3333
from ...types.axon_event_view import AxonEventView
3434
from ...types.publish_result_view import PublishResultView
3535

@@ -148,7 +148,7 @@ def list(
148148
extra_query: Query | None = None,
149149
extra_body: Body | None = None,
150150
timeout: float | httpx.Timeout | None | NotGiven = not_given,
151-
) -> AxonListView:
151+
) -> SyncAxonsCursorIDPage[AxonView]:
152152
"""
153153
[Beta] List all active axons.
154154
@@ -172,8 +172,9 @@ def list(
172172
173173
timeout: Override the client-level default timeout for this request, in seconds
174174
"""
175-
return self._get(
175+
return self._get_api_list(
176176
"/v1/axons",
177+
page=SyncAxonsCursorIDPage[AxonView],
177178
options=make_request_options(
178179
extra_headers=extra_headers,
179180
extra_query=extra_query,
@@ -190,7 +191,7 @@ def list(
190191
axon_list_params.AxonListParams,
191192
),
192193
),
193-
cast_to=AxonListView,
194+
model=AxonView,
194195
)
195196

196197
def publish(
@@ -390,7 +391,7 @@ async def retrieve(
390391
cast_to=AxonView,
391392
)
392393

393-
async def list(
394+
def list(
394395
self,
395396
*,
396397
id: str | Omit = omit,
@@ -404,7 +405,7 @@ async def list(
404405
extra_query: Query | None = None,
405406
extra_body: Body | None = None,
406407
timeout: float | httpx.Timeout | None | NotGiven = not_given,
407-
) -> AxonListView:
408+
) -> AsyncPaginator[AxonView, AsyncAxonsCursorIDPage[AxonView]]:
408409
"""
409410
[Beta] List all active axons.
410411
@@ -428,14 +429,15 @@ async def list(
428429
429430
timeout: Override the client-level default timeout for this request, in seconds
430431
"""
431-
return await self._get(
432+
return self._get_api_list(
432433
"/v1/axons",
434+
page=AsyncAxonsCursorIDPage[AxonView],
433435
options=make_request_options(
434436
extra_headers=extra_headers,
435437
extra_query=extra_query,
436438
extra_body=extra_body,
437439
timeout=timeout,
438-
query=await async_maybe_transform(
440+
query=maybe_transform(
439441
{
440442
"id": id,
441443
"include_total_count": include_total_count,
@@ -446,7 +448,7 @@ async def list(
446448
axon_list_params.AxonListParams,
447449
),
448450
),
449-
cast_to=AxonListView,
451+
model=AxonView,
450452
)
451453

452454
async def publish(

tests/api_resources/test_axons.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
from runloop_api_client import Runloop, AsyncRunloop
1212
from runloop_api_client.types import (
1313
AxonView,
14-
AxonListView,
1514
PublishResultView,
1615
)
16+
from runloop_api_client.pagination import SyncAxonsCursorIDPage, AsyncAxonsCursorIDPage
1717

1818
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
1919

@@ -94,7 +94,7 @@ def test_path_params_retrieve(self, client: Runloop) -> None:
9494
@parametrize
9595
def test_method_list(self, client: Runloop) -> None:
9696
axon = client.axons.list()
97-
assert_matches_type(AxonListView, axon, path=["response"])
97+
assert_matches_type(SyncAxonsCursorIDPage[AxonView], axon, path=["response"])
9898

9999
@parametrize
100100
def test_method_list_with_all_params(self, client: Runloop) -> None:
@@ -105,7 +105,7 @@ def test_method_list_with_all_params(self, client: Runloop) -> None:
105105
name="name",
106106
starting_after="starting_after",
107107
)
108-
assert_matches_type(AxonListView, axon, path=["response"])
108+
assert_matches_type(SyncAxonsCursorIDPage[AxonView], axon, path=["response"])
109109

110110
@parametrize
111111
def test_raw_response_list(self, client: Runloop) -> None:
@@ -114,7 +114,7 @@ def test_raw_response_list(self, client: Runloop) -> None:
114114
assert response.is_closed is True
115115
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
116116
axon = response.parse()
117-
assert_matches_type(AxonListView, axon, path=["response"])
117+
assert_matches_type(SyncAxonsCursorIDPage[AxonView], axon, path=["response"])
118118

119119
@parametrize
120120
def test_streaming_response_list(self, client: Runloop) -> None:
@@ -123,7 +123,7 @@ def test_streaming_response_list(self, client: Runloop) -> None:
123123
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
124124

125125
axon = response.parse()
126-
assert_matches_type(AxonListView, axon, path=["response"])
126+
assert_matches_type(SyncAxonsCursorIDPage[AxonView], axon, path=["response"])
127127

128128
assert cast(Any, response.is_closed) is True
129129

@@ -297,7 +297,7 @@ async def test_path_params_retrieve(self, async_client: AsyncRunloop) -> None:
297297
@parametrize
298298
async def test_method_list(self, async_client: AsyncRunloop) -> None:
299299
axon = await async_client.axons.list()
300-
assert_matches_type(AxonListView, axon, path=["response"])
300+
assert_matches_type(AsyncAxonsCursorIDPage[AxonView], axon, path=["response"])
301301

302302
@parametrize
303303
async def test_method_list_with_all_params(self, async_client: AsyncRunloop) -> None:
@@ -308,7 +308,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncRunloop) ->
308308
name="name",
309309
starting_after="starting_after",
310310
)
311-
assert_matches_type(AxonListView, axon, path=["response"])
311+
assert_matches_type(AsyncAxonsCursorIDPage[AxonView], axon, path=["response"])
312312

313313
@parametrize
314314
async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
@@ -317,7 +317,7 @@ async def test_raw_response_list(self, async_client: AsyncRunloop) -> None:
317317
assert response.is_closed is True
318318
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
319319
axon = await response.parse()
320-
assert_matches_type(AxonListView, axon, path=["response"])
320+
assert_matches_type(AsyncAxonsCursorIDPage[AxonView], axon, path=["response"])
321321

322322
@parametrize
323323
async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None:
@@ -326,7 +326,7 @@ async def test_streaming_response_list(self, async_client: AsyncRunloop) -> None
326326
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
327327

328328
axon = await response.parse()
329-
assert_matches_type(AxonListView, axon, path=["response"])
329+
assert_matches_type(AsyncAxonsCursorIDPage[AxonView], axon, path=["response"])
330330

331331
assert cast(Any, response.is_closed) is True
332332

0 commit comments

Comments
 (0)