Skip to content

Commit 9e0265f

Browse files
feat(api): manual updates
1 parent b73273f commit 9e0265f

10 files changed

Lines changed: 327 additions & 51 deletions

File tree

.stats.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 17
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-0e4d333e81e670e605a6706dbb365bc96957a59331fdc87dd1550c59880cb130.yml
3-
openapi_spec_hash: 564146e6d318ecb486ff7c0d7983b398
4-
config_hash: e3418e22e2ca1df0f8a9fcaf7153285a
1+
configured_endpoints: 18
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/beeper%2Fbeeper-desktop-api-5fb80d7f97f2428d1826b9c381476f0d46117fc694140175dbc15920b1884f1f.yml
3+
openapi_spec_hash: 06f8538bc0a27163d33a80c00fb16e86
4+
config_hash: d0b0effc89b6d81f98400536657b6876

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ and offers both synchronous and asynchronous clients powered by [httpx](https://
1111

1212
Use the Beeper Desktop MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application.
1313

14-
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40beeper%2Fdesktop-api-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBiZWVwZXIvZGVza3RvcC1hcGktbWNwIl19)
15-
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40beeper%2Fdesktop-api-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40beeper%2Fdesktop-api-mcp%22%5D%7D)
14+
[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40beeper%2Fdesktop-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBiZWVwZXIvZGVza3RvcC1tY3AiXX0)
15+
[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22%40beeper%2Fdesktop-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40beeper%2Fdesktop-mcp%22%5D%7D)
1616

1717
> Note: You may need to set environment variables in your MCP client.
1818
@@ -23,10 +23,13 @@ The REST API documentation can be found on [developers.beeper.com](https://devel
2323
## Installation
2424

2525
```sh
26-
# install from PyPI
27-
pip install beeper_desktop_api
26+
# install from the production repo
27+
pip install git+ssh://git@github.com/beeper/desktop-api-python.git
2828
```
2929

30+
> [!NOTE]
31+
> Once this package is [published to PyPI](https://www.stainless.com/docs/guides/publish), this will become: `pip install beeper_desktop_api`
32+
3033
## Usage
3134

3235
The full API of this library can be found in [api.md](api.md).
@@ -87,8 +90,8 @@ By default, the async client uses `httpx` for HTTP requests. However, for improv
8790
You can enable this by installing `aiohttp`:
8891

8992
```sh
90-
# install from PyPI
91-
pip install beeper_desktop_api[aiohttp]
93+
# install from the production repo
94+
pip install 'beeper_desktop_api[aiohttp] @ git+ssh://git@github.com/beeper/desktop-api-python.git'
9295
```
9396

9497
Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`:
@@ -221,6 +224,23 @@ client.chats.reminders.create(
221224
)
222225
```
223226

227+
## File uploads
228+
229+
Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`.
230+
231+
```python
232+
from pathlib import Path
233+
from beeper_desktop_api import BeeperDesktop
234+
235+
client = BeeperDesktop()
236+
237+
client.assets.upload(
238+
file=Path("/path/to/file"),
239+
)
240+
```
241+
242+
The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically.
243+
224244
## Handling errors
225245

226246
When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `beeper_desktop_api.APIConnectionError` is raised.

api.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,15 @@ Methods:
8484
Types:
8585

8686
```python
87-
from beeper_desktop_api.types import AssetDownloadResponse, AssetUploadResponse
87+
from beeper_desktop_api.types import (
88+
AssetDownloadResponse,
89+
AssetUploadResponse,
90+
AssetUploadBase64Response,
91+
)
8892
```
8993

9094
Methods:
9195

9296
- <code title="post /v1/assets/download">client.assets.<a href="./src/beeper_desktop_api/resources/assets.py">download</a>(\*\*<a href="src/beeper_desktop_api/types/asset_download_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/asset_download_response.py">AssetDownloadResponse</a></code>
9397
- <code title="post /v1/assets/upload">client.assets.<a href="./src/beeper_desktop_api/resources/assets.py">upload</a>(\*\*<a href="src/beeper_desktop_api/types/asset_upload_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/asset_upload_response.py">AssetUploadResponse</a></code>
98+
- <code title="post /v1/assets/upload/base64">client.assets.<a href="./src/beeper_desktop_api/resources/assets.py">upload_base64</a>(\*\*<a href="src/beeper_desktop_api/types/asset_upload_base64_params.py">params</a>) -> <a href="./src/beeper_desktop_api/types/asset_upload_base64_response.py">AssetUploadBase64Response</a></code>

src/beeper_desktop_api/_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None:
3434
if not is_file_content(obj):
3535
prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`"
3636
raise RuntimeError(
37-
f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead."
37+
f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/beeper/desktop-api-python/tree/main#file-uploads"
3838
) from None
3939

4040

src/beeper_desktop_api/resources/assets.py

Lines changed: 137 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
import httpx
88

9-
from ..types import asset_upload_params, asset_download_params
10-
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
9+
from ..types import asset_upload_params, asset_download_params, asset_upload_base64_params
10+
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
1111
from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
1212
from .._compat import cached_property
1313
from .._resource import SyncAPIResource, AsyncAPIResource
@@ -20,6 +20,7 @@
2020
from .._base_client import make_request_options
2121
from ..types.asset_upload_response import AssetUploadResponse
2222
from ..types.asset_download_response import AssetDownloadResponse
23+
from ..types.asset_upload_base64_response import AssetUploadBase64Response
2324

2425
__all__ = ["AssetsResource", "AsyncAssetsResource"]
2526

@@ -84,7 +85,7 @@ def download(
8485
def upload(
8586
self,
8687
*,
87-
content: str,
88+
file: FileTypes,
8889
file_name: str | Omit = omit,
8990
mime_type: str | Omit = omit,
9091
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -94,16 +95,15 @@ def upload(
9495
extra_body: Body | None = None,
9596
timeout: float | httpx.Timeout | None | NotGiven = not_given,
9697
) -> AssetUploadResponse:
97-
"""Upload a file to a temporary location.
98+
"""Upload a file to a temporary location using multipart/form-data.
9899
99-
Supports JSON body with base64 `content`
100-
field, or multipart/form-data with `file` field. Returns a local file URL that
101-
can be used when sending messages with attachments.
100+
Returns an
101+
uploadID that can be referenced when sending messages with attachments.
102102
103103
Args:
104-
content: Base64-encoded file content (max ~500MB decoded)
104+
file: The file to upload (max 500 MB).
105105
106-
file_name: Original filename. Generated if omitted
106+
file_name: Original filename. Defaults to the uploaded file name if omitted
107107
108108
mime_type: MIME type. Auto-detected from magic bytes if omitted
109109
@@ -117,17 +117,16 @@ def upload(
117117
"""
118118
body = deepcopy_minimal(
119119
{
120-
"content": content,
120+
"file": file,
121121
"file_name": file_name,
122122
"mime_type": mime_type,
123123
}
124124
)
125125
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
126-
if files:
127-
# It should be noted that the actual Content-Type header that will be
128-
# sent to the server will contain a `boundary` parameter, e.g.
129-
# multipart/form-data; boundary=---abc--
130-
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
126+
# It should be noted that the actual Content-Type header that will be
127+
# sent to the server will contain a `boundary` parameter, e.g.
128+
# multipart/form-data; boundary=---abc--
129+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
131130
return self._post(
132131
"/v1/assets/upload",
133132
body=maybe_transform(body, asset_upload_params.AssetUploadParams),
@@ -138,6 +137,56 @@ def upload(
138137
cast_to=AssetUploadResponse,
139138
)
140139

140+
def upload_base64(
141+
self,
142+
*,
143+
content: str,
144+
file_name: str | Omit = omit,
145+
mime_type: str | Omit = omit,
146+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
147+
# The extra values given here take precedence over values defined on the client or passed to this method.
148+
extra_headers: Headers | None = None,
149+
extra_query: Query | None = None,
150+
extra_body: Body | None = None,
151+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
152+
) -> AssetUploadBase64Response:
153+
"""Upload a file using a JSON body with base64-encoded content.
154+
155+
Returns an uploadID
156+
that can be referenced when sending messages with attachments. Alternative to
157+
the multipart upload endpoint.
158+
159+
Args:
160+
content: Base64-encoded file content (max ~500MB decoded)
161+
162+
file_name: Original filename. Generated if omitted
163+
164+
mime_type: MIME type. Auto-detected from magic bytes if omitted
165+
166+
extra_headers: Send extra headers
167+
168+
extra_query: Add additional query parameters to the request
169+
170+
extra_body: Add additional JSON properties to the request
171+
172+
timeout: Override the client-level default timeout for this request, in seconds
173+
"""
174+
return self._post(
175+
"/v1/assets/upload/base64",
176+
body=maybe_transform(
177+
{
178+
"content": content,
179+
"file_name": file_name,
180+
"mime_type": mime_type,
181+
},
182+
asset_upload_base64_params.AssetUploadBase64Params,
183+
),
184+
options=make_request_options(
185+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
186+
),
187+
cast_to=AssetUploadBase64Response,
188+
)
189+
141190

142191
class AsyncAssetsResource(AsyncAPIResource):
143192
"""Manage assets in Beeper Desktop, like message attachments"""
@@ -199,7 +248,7 @@ async def download(
199248
async def upload(
200249
self,
201250
*,
202-
content: str,
251+
file: FileTypes,
203252
file_name: str | Omit = omit,
204253
mime_type: str | Omit = omit,
205254
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -209,16 +258,15 @@ async def upload(
209258
extra_body: Body | None = None,
210259
timeout: float | httpx.Timeout | None | NotGiven = not_given,
211260
) -> AssetUploadResponse:
212-
"""Upload a file to a temporary location.
261+
"""Upload a file to a temporary location using multipart/form-data.
213262
214-
Supports JSON body with base64 `content`
215-
field, or multipart/form-data with `file` field. Returns a local file URL that
216-
can be used when sending messages with attachments.
263+
Returns an
264+
uploadID that can be referenced when sending messages with attachments.
217265
218266
Args:
219-
content: Base64-encoded file content (max ~500MB decoded)
267+
file: The file to upload (max 500 MB).
220268
221-
file_name: Original filename. Generated if omitted
269+
file_name: Original filename. Defaults to the uploaded file name if omitted
222270
223271
mime_type: MIME type. Auto-detected from magic bytes if omitted
224272
@@ -232,17 +280,16 @@ async def upload(
232280
"""
233281
body = deepcopy_minimal(
234282
{
235-
"content": content,
283+
"file": file,
236284
"file_name": file_name,
237285
"mime_type": mime_type,
238286
}
239287
)
240288
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
241-
if files:
242-
# It should be noted that the actual Content-Type header that will be
243-
# sent to the server will contain a `boundary` parameter, e.g.
244-
# multipart/form-data; boundary=---abc--
245-
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
289+
# It should be noted that the actual Content-Type header that will be
290+
# sent to the server will contain a `boundary` parameter, e.g.
291+
# multipart/form-data; boundary=---abc--
292+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
246293
return await self._post(
247294
"/v1/assets/upload",
248295
body=await async_maybe_transform(body, asset_upload_params.AssetUploadParams),
@@ -253,6 +300,56 @@ async def upload(
253300
cast_to=AssetUploadResponse,
254301
)
255302

303+
async def upload_base64(
304+
self,
305+
*,
306+
content: str,
307+
file_name: str | Omit = omit,
308+
mime_type: str | Omit = omit,
309+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
310+
# The extra values given here take precedence over values defined on the client or passed to this method.
311+
extra_headers: Headers | None = None,
312+
extra_query: Query | None = None,
313+
extra_body: Body | None = None,
314+
timeout: float | httpx.Timeout | None | NotGiven = not_given,
315+
) -> AssetUploadBase64Response:
316+
"""Upload a file using a JSON body with base64-encoded content.
317+
318+
Returns an uploadID
319+
that can be referenced when sending messages with attachments. Alternative to
320+
the multipart upload endpoint.
321+
322+
Args:
323+
content: Base64-encoded file content (max ~500MB decoded)
324+
325+
file_name: Original filename. Generated if omitted
326+
327+
mime_type: MIME type. Auto-detected from magic bytes if omitted
328+
329+
extra_headers: Send extra headers
330+
331+
extra_query: Add additional query parameters to the request
332+
333+
extra_body: Add additional JSON properties to the request
334+
335+
timeout: Override the client-level default timeout for this request, in seconds
336+
"""
337+
return await self._post(
338+
"/v1/assets/upload/base64",
339+
body=await async_maybe_transform(
340+
{
341+
"content": content,
342+
"file_name": file_name,
343+
"mime_type": mime_type,
344+
},
345+
asset_upload_base64_params.AssetUploadBase64Params,
346+
),
347+
options=make_request_options(
348+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
349+
),
350+
cast_to=AssetUploadBase64Response,
351+
)
352+
256353

257354
class AssetsResourceWithRawResponse:
258355
def __init__(self, assets: AssetsResource) -> None:
@@ -264,6 +361,9 @@ def __init__(self, assets: AssetsResource) -> None:
264361
self.upload = to_raw_response_wrapper(
265362
assets.upload,
266363
)
364+
self.upload_base64 = to_raw_response_wrapper(
365+
assets.upload_base64,
366+
)
267367

268368

269369
class AsyncAssetsResourceWithRawResponse:
@@ -276,6 +376,9 @@ def __init__(self, assets: AsyncAssetsResource) -> None:
276376
self.upload = async_to_raw_response_wrapper(
277377
assets.upload,
278378
)
379+
self.upload_base64 = async_to_raw_response_wrapper(
380+
assets.upload_base64,
381+
)
279382

280383

281384
class AssetsResourceWithStreamingResponse:
@@ -288,6 +391,9 @@ def __init__(self, assets: AssetsResource) -> None:
288391
self.upload = to_streamed_response_wrapper(
289392
assets.upload,
290393
)
394+
self.upload_base64 = to_streamed_response_wrapper(
395+
assets.upload_base64,
396+
)
291397

292398

293399
class AsyncAssetsResourceWithStreamingResponse:
@@ -300,3 +406,6 @@ def __init__(self, assets: AsyncAssetsResource) -> None:
300406
self.upload = async_to_streamed_response_wrapper(
301407
assets.upload,
302408
)
409+
self.upload_base64 = async_to_streamed_response_wrapper(
410+
assets.upload_base64,
411+
)

src/beeper_desktop_api/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,5 @@
2727
from .message_update_params import MessageUpdateParams as MessageUpdateParams
2828
from .asset_download_response import AssetDownloadResponse as AssetDownloadResponse
2929
from .message_update_response import MessageUpdateResponse as MessageUpdateResponse
30+
from .asset_upload_base64_params import AssetUploadBase64Params as AssetUploadBase64Params
31+
from .asset_upload_base64_response import AssetUploadBase64Response as AssetUploadBase64Response

0 commit comments

Comments
 (0)