Skip to content

Commit 48b4b7f

Browse files
feat(api): manual updates
1 parent c9f3b2d commit 48b4b7f

File tree

8 files changed

+104
-123
lines changed

8 files changed

+104
-123
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: 15
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-953cbc1ea1fe675bf2d32b18030a3ac509c521946921cb338c0d1c2cfef89424.yml
3-
openapi_spec_hash: b4d08ca2dc21bc00245c9c9408be89ef
4-
config_hash: 4fb2010b528ce4358300ddd10e750265
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-803a4423d75f7a43582319924f0770153fd5ec313b9466c290513b9a891c2653.yml
3+
openapi_spec_hash: f32dfbf172bb043fd8c961cba5f73765
4+
config_hash: 738402ade5ac9528c8ef1677aa1d70f7

README.md

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

119119
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`.
120120

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

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

api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ Methods:
7070
Types:
7171

7272
```python
73-
from beeper_desktop_api.types import MessageSearchResponse, MessageSendResponse
73+
from beeper_desktop_api.types import MessageSendResponse
7474
```
7575

7676
Methods:
7777

7878
- <code title="get /v1/messages">client.messages.<a href="./src/beeper_desktop_api/resources/messages.py">list</a>(\*\*<a href="src/beeper_desktop_api/types/message_list_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/shared/message.py">SyncCursor[Message]</a></code>
79-
- <code title="get /v1/messages/search">client.messages.<a href="./src/beeper_desktop_api/resources/messages.py">search</a>(\*\*<a href="src/beeper_desktop_api/types/message_search_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/message_search_response.py">MessageSearchResponse</a></code>
79+
- <code title="get /v1/messages/search">client.messages.<a href="./src/beeper_desktop_api/resources/messages.py">search</a>(\*\*<a href="src/beeper_desktop_api/types/message_search_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/shared/message.py">SyncCursor[Message]</a></code>
8080
- <code title="post /v1/messages">client.messages.<a href="./src/beeper_desktop_api/resources/messages.py">send</a>(\*\*<a href="src/beeper_desktop_api/types/message_send_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/message_send_response.py">MessageSendResponse</a></code>
Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

3-
from typing import Dict, List, Generic, TypeVar, Optional
3+
from typing import List, Generic, TypeVar, Optional
44
from typing_extensions import override
55

66
from pydantic import Field as FieldInfo
77

8-
from .types.chat import Chat
98
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
109

11-
__all__ = ["SyncCursor", "AsyncCursor", "SyncCursorWithChats", "AsyncCursorWithChats"]
10+
__all__ = ["SyncCursor", "AsyncCursor"]
1211

1312
_T = TypeVar("_T")
1413

@@ -71,65 +70,3 @@ def next_page_info(self) -> Optional[PageInfo]:
7170
return None
7271

7372
return PageInfo(params={"cursor": oldest_cursor})
74-
75-
76-
class SyncCursorWithChats(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
77-
items: List[_T]
78-
chats: Optional[Dict[str, Chat]] = None
79-
has_more: Optional[bool] = FieldInfo(alias="hasMore", default=None)
80-
oldest_cursor: Optional[str] = FieldInfo(alias="oldestCursor", default=None)
81-
newest_cursor: Optional[str] = FieldInfo(alias="newestCursor", default=None)
82-
83-
@override
84-
def _get_page_items(self) -> List[_T]:
85-
items = self.items
86-
if not items:
87-
return []
88-
return items
89-
90-
@override
91-
def has_next_page(self) -> bool:
92-
has_more = self.has_more
93-
if has_more is not None and has_more is False:
94-
return False
95-
96-
return super().has_next_page()
97-
98-
@override
99-
def next_page_info(self) -> Optional[PageInfo]:
100-
oldest_cursor = self.oldest_cursor
101-
if not oldest_cursor:
102-
return None
103-
104-
return PageInfo(params={"cursor": oldest_cursor})
105-
106-
107-
class AsyncCursorWithChats(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
108-
items: List[_T]
109-
chats: Optional[Dict[str, Chat]] = None
110-
has_more: Optional[bool] = FieldInfo(alias="hasMore", default=None)
111-
oldest_cursor: Optional[str] = FieldInfo(alias="oldestCursor", default=None)
112-
newest_cursor: Optional[str] = FieldInfo(alias="newestCursor", default=None)
113-
114-
@override
115-
def _get_page_items(self) -> List[_T]:
116-
items = self.items
117-
if not items:
118-
return []
119-
return items
120-
121-
@override
122-
def has_next_page(self) -> bool:
123-
has_more = self.has_more
124-
if has_more is not None and has_more is False:
125-
return False
126-
127-
return super().has_next_page()
128-
129-
@override
130-
def next_page_info(self) -> Optional[PageInfo]:
131-
oldest_cursor = self.oldest_cursor
132-
if not oldest_cursor:
133-
return None
134-
135-
return PageInfo(params={"cursor": oldest_cursor})

src/beeper_desktop_api/resources/messages.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from .._base_client import AsyncPaginator, make_request_options
2424
from ..types.shared.message import Message
2525
from ..types.message_send_response import MessageSendResponse
26-
from ..types.message_search_response import MessageSearchResponse
2726

2827
__all__ = ["MessagesResource", "AsyncMessagesResource"]
2928

@@ -130,7 +129,7 @@ def search(
130129
extra_query: Query | None = None,
131130
extra_body: Body | None = None,
132131
timeout: float | httpx.Timeout | None | NotGiven = not_given,
133-
) -> MessageSearchResponse:
132+
) -> SyncCursor[Message]:
134133
"""
135134
Search messages across chats using Beeper's message index
136135
@@ -180,8 +179,9 @@ def search(
180179
181180
timeout: Override the client-level default timeout for this request, in seconds
182181
"""
183-
return self._get(
182+
return self._get_api_list(
184183
"/v1/messages/search",
184+
page=SyncCursor[Message],
185185
options=make_request_options(
186186
extra_headers=extra_headers,
187187
extra_query=extra_query,
@@ -206,7 +206,7 @@ def search(
206206
message_search_params.MessageSearchParams,
207207
),
208208
),
209-
cast_to=MessageSearchResponse,
209+
model=Message,
210210
)
211211

212212
def send(
@@ -339,7 +339,7 @@ def list(
339339
model=Message,
340340
)
341341

342-
async def search(
342+
def search(
343343
self,
344344
*,
345345
account_ids: SequenceNotStr[str] | Omit = omit,
@@ -361,7 +361,7 @@ async def search(
361361
extra_query: Query | None = None,
362362
extra_body: Body | None = None,
363363
timeout: float | httpx.Timeout | None | NotGiven = not_given,
364-
) -> MessageSearchResponse:
364+
) -> AsyncPaginator[Message, AsyncCursor[Message]]:
365365
"""
366366
Search messages across chats using Beeper's message index
367367
@@ -411,14 +411,15 @@ async def search(
411411
412412
timeout: Override the client-level default timeout for this request, in seconds
413413
"""
414-
return await self._get(
414+
return self._get_api_list(
415415
"/v1/messages/search",
416+
page=AsyncCursor[Message],
416417
options=make_request_options(
417418
extra_headers=extra_headers,
418419
extra_query=extra_query,
419420
extra_body=extra_body,
420421
timeout=timeout,
421-
query=await async_maybe_transform(
422+
query=maybe_transform(
422423
{
423424
"account_ids": account_ids,
424425
"chat_ids": chat_ids,
@@ -437,7 +438,7 @@ async def search(
437438
message_search_params.MessageSearchParams,
438439
),
439440
),
440-
cast_to=MessageSearchResponse,
441+
model=Message,
441442
)
442443

443444
async def send(

src/beeper_desktop_api/types/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,4 @@
3131
from .message_send_response import MessageSendResponse as MessageSendResponse
3232
from .contact_search_response import ContactSearchResponse as ContactSearchResponse
3333
from .download_asset_response import DownloadAssetResponse as DownloadAssetResponse
34-
from .message_search_response import MessageSearchResponse as MessageSearchResponse
3534
from .client_download_asset_params import ClientDownloadAssetParams as ClientDownloadAssetParams

src/beeper_desktop_api/types/message_search_response.py

Lines changed: 0 additions & 34 deletions
This file was deleted.

tests/api_resources/test_messages.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from beeper_desktop_api import BeeperDesktop, AsyncBeeperDesktop
1212
from beeper_desktop_api.types import (
1313
MessageSendResponse,
14-
MessageSearchResponse,
1514
)
1615
from beeper_desktop_api._utils import parse_datetime
1716
from beeper_desktop_api.pagination import SyncCursor, AsyncCursor
@@ -67,7 +66,7 @@ def test_streaming_response_list(self, client: BeeperDesktop) -> None:
6766
@parametrize
6867
def test_method_search(self, client: BeeperDesktop) -> None:
6968
message = client.messages.search()
70-
assert_matches_type(MessageSearchResponse, message, path=["response"])
69+
assert_matches_type(SyncCursor[Message], message, path=["response"])
7170

7271
@parametrize
7372
def test_method_search_with_all_params(self, client: BeeperDesktop) -> None:
@@ -90,7 +89,7 @@ def test_method_search_with_all_params(self, client: BeeperDesktop) -> None:
9089
query="dinner",
9190
sender="me",
9291
)
93-
assert_matches_type(MessageSearchResponse, message, path=["response"])
92+
assert_matches_type(SyncCursor[Message], message, path=["response"])
9493

9594
@parametrize
9695
def test_raw_response_search(self, client: BeeperDesktop) -> None:
@@ -99,7 +98,7 @@ def test_raw_response_search(self, client: BeeperDesktop) -> None:
9998
assert response.is_closed is True
10099
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
101100
message = response.parse()
102-
assert_matches_type(MessageSearchResponse, message, path=["response"])
101+
assert_matches_type(SyncCursor[Message], message, path=["response"])
103102

104103
@parametrize
105104
def test_streaming_response_search(self, client: BeeperDesktop) -> None:
@@ -108,7 +107,7 @@ def test_streaming_response_search(self, client: BeeperDesktop) -> None:
108107
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
109108

110109
message = response.parse()
111-
assert_matches_type(MessageSearchResponse, message, path=["response"])
110+
assert_matches_type(SyncCursor[Message], message, path=["response"])
112111

113112
assert cast(Any, response.is_closed) is True
114113

@@ -202,7 +201,7 @@ async def test_streaming_response_list(self, async_client: AsyncBeeperDesktop) -
202201
@parametrize
203202
async def test_method_search(self, async_client: AsyncBeeperDesktop) -> None:
204203
message = await async_client.messages.search()
205-
assert_matches_type(MessageSearchResponse, message, path=["response"])
204+
assert_matches_type(AsyncCursor[Message], message, path=["response"])
206205

207206
@parametrize
208207
async def test_method_search_with_all_params(self, async_client: AsyncBeeperDesktop) -> None:
@@ -225,7 +224,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncBeeperDesk
225224
query="dinner",
226225
sender="me",
227226
)
228-
assert_matches_type(MessageSearchResponse, message, path=["response"])
227+
assert_matches_type(AsyncCursor[Message], message, path=["response"])
229228

230229
@parametrize
231230
async def test_raw_response_search(self, async_client: AsyncBeeperDesktop) -> None:
@@ -234,7 +233,7 @@ async def test_raw_response_search(self, async_client: AsyncBeeperDesktop) -> No
234233
assert response.is_closed is True
235234
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
236235
message = await response.parse()
237-
assert_matches_type(MessageSearchResponse, message, path=["response"])
236+
assert_matches_type(AsyncCursor[Message], message, path=["response"])
238237

239238
@parametrize
240239
async def test_streaming_response_search(self, async_client: AsyncBeeperDesktop) -> None:
@@ -243,7 +242,7 @@ async def test_streaming_response_search(self, async_client: AsyncBeeperDesktop)
243242
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
244243

245244
message = await response.parse()
246-
assert_matches_type(MessageSearchResponse, message, path=["response"])
245+
assert_matches_type(AsyncCursor[Message], message, path=["response"])
247246

248247
assert cast(Any, response.is_closed) is True
249248

0 commit comments

Comments
 (0)