Skip to content

Commit 10e12a2

Browse files
feat(api): api update
1 parent e8da506 commit 10e12a2

File tree

9 files changed

+218
-9
lines changed

9 files changed

+218
-9
lines changed

.stats.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 8
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-4c45e387cbdc7c80d75cdb8eb924cf92a3a48a0c10060fda917b83a7e454aef5.yml
3-
openapi_spec_hash: c859ac2e3429ad3663337b99c722f317
1+
configured_endpoints: 9
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-0e2874da641d72b5833ebef8cc792d86250d397b96eeedba7d4759ffabc076de.yml
3+
openapi_spec_hash: f13ea02b49134e11025cb18f3d45d313
44
config_hash: 8477e3ee6fd596ab6ac911d052e4de79

README.md

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

112112
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`.
113113

114+
## File uploads
115+
116+
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)`.
117+
118+
```python
119+
from pathlib import Path
120+
from supermemory import Supermemory
121+
122+
client = Supermemory()
123+
124+
client.memories.upload_file(
125+
file=Path("/path/to/file"),
126+
)
127+
```
128+
129+
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.
130+
114131
## Handling errors
115132

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

api.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
Types:
44

55
```python
6-
from supermemory.types import MemoryUpdateResponse, MemoryAddResponse, MemoryGetResponse
6+
from supermemory.types import (
7+
MemoryUpdateResponse,
8+
MemoryAddResponse,
9+
MemoryGetResponse,
10+
MemoryUploadFileResponse,
11+
)
712
```
813

914
Methods:
@@ -12,6 +17,7 @@ Methods:
1217
- <code title="delete /v3/memories/{id}">client.memories.<a href="./src/supermemory/resources/memories.py">delete</a>(id) -> None</code>
1318
- <code title="post /v3/memories">client.memories.<a href="./src/supermemory/resources/memories.py">add</a>(\*\*<a href="src/supermemory/types/memory_add_params.py">params</a>) -> <a href="./src/supermemory/types/memory_add_response.py">MemoryAddResponse</a></code>
1419
- <code title="get /v3/memories/{id}">client.memories.<a href="./src/supermemory/resources/memories.py">get</a>(id) -> <a href="./src/supermemory/types/memory_get_response.py">MemoryGetResponse</a></code>
20+
- <code title="post /v3/memories/file">client.memories.<a href="./src/supermemory/resources/memories.py">upload_file</a>(\*\*<a href="src/supermemory/types/memory_upload_file_params.py">params</a>) -> <a href="./src/supermemory/types/memory_upload_file_response.py">MemoryUploadFileResponse</a></code>
1521

1622
# Settings
1723

src/supermemory/_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/supermemoryai/python-sdk/tree/main#file-uploads"
3838
) from None
3939

4040

src/supermemory/resources/memories.py

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
from __future__ import annotations
44

5-
from typing import Dict, List, Union
5+
from typing import Dict, List, Union, Mapping, cast
66

77
import httpx
88

9-
from ..types import memory_add_params, memory_update_params
10-
from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
11-
from .._utils import maybe_transform, async_maybe_transform
9+
from ..types import memory_add_params, memory_update_params, memory_upload_file_params
10+
from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, FileTypes
11+
from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
1212
from .._compat import cached_property
1313
from .._resource import SyncAPIResource, AsyncAPIResource
1414
from .._response import (
@@ -21,6 +21,7 @@
2121
from ..types.memory_add_response import MemoryAddResponse
2222
from ..types.memory_get_response import MemoryGetResponse
2323
from ..types.memory_update_response import MemoryUpdateResponse
24+
from ..types.memory_upload_file_response import MemoryUploadFileResponse
2425

2526
__all__ = ["MemoriesResource", "AsyncMemoriesResource"]
2627

@@ -243,6 +244,45 @@ def get(
243244
cast_to=MemoryGetResponse,
244245
)
245246

247+
def upload_file(
248+
self,
249+
*,
250+
file: FileTypes,
251+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
252+
# The extra values given here take precedence over values defined on the client or passed to this method.
253+
extra_headers: Headers | None = None,
254+
extra_query: Query | None = None,
255+
extra_body: Body | None = None,
256+
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
257+
) -> MemoryUploadFileResponse:
258+
"""
259+
Upload a file to be processed
260+
261+
Args:
262+
extra_headers: Send extra headers
263+
264+
extra_query: Add additional query parameters to the request
265+
266+
extra_body: Add additional JSON properties to the request
267+
268+
timeout: Override the client-level default timeout for this request, in seconds
269+
"""
270+
body = deepcopy_minimal({"file": file})
271+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
272+
# It should be noted that the actual Content-Type header that will be
273+
# sent to the server will contain a `boundary` parameter, e.g.
274+
# multipart/form-data; boundary=---abc--
275+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
276+
return self._post(
277+
"/v3/memories/file",
278+
body=maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams),
279+
files=files,
280+
options=make_request_options(
281+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
282+
),
283+
cast_to=MemoryUploadFileResponse,
284+
)
285+
246286

247287
class AsyncMemoriesResource(AsyncAPIResource):
248288
@cached_property
@@ -462,6 +502,45 @@ async def get(
462502
cast_to=MemoryGetResponse,
463503
)
464504

505+
async def upload_file(
506+
self,
507+
*,
508+
file: FileTypes,
509+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
510+
# The extra values given here take precedence over values defined on the client or passed to this method.
511+
extra_headers: Headers | None = None,
512+
extra_query: Query | None = None,
513+
extra_body: Body | None = None,
514+
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
515+
) -> MemoryUploadFileResponse:
516+
"""
517+
Upload a file to be processed
518+
519+
Args:
520+
extra_headers: Send extra headers
521+
522+
extra_query: Add additional query parameters to the request
523+
524+
extra_body: Add additional JSON properties to the request
525+
526+
timeout: Override the client-level default timeout for this request, in seconds
527+
"""
528+
body = deepcopy_minimal({"file": file})
529+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
530+
# It should be noted that the actual Content-Type header that will be
531+
# sent to the server will contain a `boundary` parameter, e.g.
532+
# multipart/form-data; boundary=---abc--
533+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
534+
return await self._post(
535+
"/v3/memories/file",
536+
body=await async_maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams),
537+
files=files,
538+
options=make_request_options(
539+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
540+
),
541+
cast_to=MemoryUploadFileResponse,
542+
)
543+
465544

466545
class MemoriesResourceWithRawResponse:
467546
def __init__(self, memories: MemoriesResource) -> None:
@@ -479,6 +558,9 @@ def __init__(self, memories: MemoriesResource) -> None:
479558
self.get = to_raw_response_wrapper(
480559
memories.get,
481560
)
561+
self.upload_file = to_raw_response_wrapper(
562+
memories.upload_file,
563+
)
482564

483565

484566
class AsyncMemoriesResourceWithRawResponse:
@@ -497,6 +579,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
497579
self.get = async_to_raw_response_wrapper(
498580
memories.get,
499581
)
582+
self.upload_file = async_to_raw_response_wrapper(
583+
memories.upload_file,
584+
)
500585

501586

502587
class MemoriesResourceWithStreamingResponse:
@@ -515,6 +600,9 @@ def __init__(self, memories: MemoriesResource) -> None:
515600
self.get = to_streamed_response_wrapper(
516601
memories.get,
517602
)
603+
self.upload_file = to_streamed_response_wrapper(
604+
memories.upload_file,
605+
)
518606

519607

520608
class AsyncMemoriesResourceWithStreamingResponse:
@@ -533,3 +621,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
533621
self.get = async_to_streamed_response_wrapper(
534622
memories.get,
535623
)
624+
self.upload_file = async_to_streamed_response_wrapper(
625+
memories.upload_file,
626+
)

src/supermemory/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@
1212
from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse
1313
from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse
1414
from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams
15+
from .memory_upload_file_params import MemoryUploadFileParams as MemoryUploadFileParams
1516
from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse
17+
from .memory_upload_file_response import MemoryUploadFileResponse as MemoryUploadFileResponse
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
from __future__ import annotations
4+
5+
from typing_extensions import Required, TypedDict
6+
7+
from .._types import FileTypes
8+
9+
__all__ = ["MemoryUploadFileParams"]
10+
11+
12+
class MemoryUploadFileParams(TypedDict, total=False):
13+
file: Required[FileTypes]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
from .._models import BaseModel
4+
5+
__all__ = ["MemoryUploadFileResponse"]
6+
7+
8+
class MemoryUploadFileResponse(BaseModel):
9+
id: str
10+
11+
status: str

tests/api_resources/test_memories.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
MemoryAddResponse,
1414
MemoryGetResponse,
1515
MemoryUpdateResponse,
16+
MemoryUploadFileResponse,
1617
)
1718

1819
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -222,6 +223,40 @@ def test_path_params_get(self, client: Supermemory) -> None:
222223
"",
223224
)
224225

226+
@pytest.mark.skip()
227+
@parametrize
228+
def test_method_upload_file(self, client: Supermemory) -> None:
229+
memory = client.memories.upload_file(
230+
file=b"raw file contents",
231+
)
232+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
233+
234+
@pytest.mark.skip()
235+
@parametrize
236+
def test_raw_response_upload_file(self, client: Supermemory) -> None:
237+
response = client.memories.with_raw_response.upload_file(
238+
file=b"raw file contents",
239+
)
240+
241+
assert response.is_closed is True
242+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
243+
memory = response.parse()
244+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
245+
246+
@pytest.mark.skip()
247+
@parametrize
248+
def test_streaming_response_upload_file(self, client: Supermemory) -> None:
249+
with client.memories.with_streaming_response.upload_file(
250+
file=b"raw file contents",
251+
) as response:
252+
assert not response.is_closed
253+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
254+
255+
memory = response.parse()
256+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
257+
258+
assert cast(Any, response.is_closed) is True
259+
225260

226261
class TestAsyncMemories:
227262
parametrize = pytest.mark.parametrize(
@@ -428,3 +463,37 @@ async def test_path_params_get(self, async_client: AsyncSupermemory) -> None:
428463
await async_client.memories.with_raw_response.get(
429464
"",
430465
)
466+
467+
@pytest.mark.skip()
468+
@parametrize
469+
async def test_method_upload_file(self, async_client: AsyncSupermemory) -> None:
470+
memory = await async_client.memories.upload_file(
471+
file=b"raw file contents",
472+
)
473+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
474+
475+
@pytest.mark.skip()
476+
@parametrize
477+
async def test_raw_response_upload_file(self, async_client: AsyncSupermemory) -> None:
478+
response = await async_client.memories.with_raw_response.upload_file(
479+
file=b"raw file contents",
480+
)
481+
482+
assert response.is_closed is True
483+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
484+
memory = await response.parse()
485+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
486+
487+
@pytest.mark.skip()
488+
@parametrize
489+
async def test_streaming_response_upload_file(self, async_client: AsyncSupermemory) -> None:
490+
async with async_client.memories.with_streaming_response.upload_file(
491+
file=b"raw file contents",
492+
) as response:
493+
assert not response.is_closed
494+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
495+
496+
memory = await response.parse()
497+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
498+
499+
assert cast(Any, response.is_closed) is True

0 commit comments

Comments
 (0)