Skip to content

Commit fa75aff

Browse files
feat(api): manual updates
1 parent b31cccb commit fa75aff

File tree

9 files changed

+245
-7
lines changed

9 files changed

+245
-7
lines changed

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
configured_endpoints: 16
1+
configured_endpoints: 17
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-d52acd1a525b4bfe9f4befcc3a645f5d1289d75e7bad999cf1330e539b2ed84e.yml
33
openapi_spec_hash: c34df5406cfa4d245812d30f99d28116
4-
config_hash: be10c837d5319a33f30809a3ec223caf
4+
config_hash: 38ef57207cda6bd69b47a02098e2fdb0

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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from supermemory.types import (
88
MemoryListResponse,
99
MemoryAddResponse,
1010
MemoryGetResponse,
11+
MemoryUploadFileResponse,
1112
)
1213
```
1314

@@ -18,6 +19,7 @@ Methods:
1819
- <code title="delete /v3/memories/{id}">client.memories.<a href="./src/supermemory/resources/memories.py">delete</a>(id) -> None</code>
1920
- <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>
2021
- <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>
22+
- <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>
2123

2224
# Search
2325

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: 107 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
from __future__ import annotations
44

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

88
import httpx
99

10-
from ..types import memory_add_params, memory_list_params, memory_update_params
11-
from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven
12-
from .._utils import maybe_transform, async_maybe_transform
10+
from ..types import memory_add_params, memory_list_params, memory_update_params, memory_upload_file_params
11+
from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven, FileTypes
12+
from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform
1313
from .._compat import cached_property
1414
from .._resource import SyncAPIResource, AsyncAPIResource
1515
from .._response import (
@@ -23,6 +23,7 @@
2323
from ..types.memory_get_response import MemoryGetResponse
2424
from ..types.memory_list_response import MemoryListResponse
2525
from ..types.memory_update_response import MemoryUpdateResponse
26+
from ..types.memory_upload_file_response import MemoryUploadFileResponse
2627

2728
__all__ = ["MemoriesResource", "AsyncMemoriesResource"]
2829

@@ -305,6 +306,51 @@ def get(
305306
cast_to=MemoryGetResponse,
306307
)
307308

309+
def upload_file(
310+
self,
311+
*,
312+
file: FileTypes,
313+
container_tags: str | NotGiven = NOT_GIVEN,
314+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
315+
# The extra values given here take precedence over values defined on the client or passed to this method.
316+
extra_headers: Headers | None = None,
317+
extra_query: Query | None = None,
318+
extra_body: Body | None = None,
319+
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
320+
) -> MemoryUploadFileResponse:
321+
"""
322+
Upload a file to be processed
323+
324+
Args:
325+
extra_headers: Send extra headers
326+
327+
extra_query: Add additional query parameters to the request
328+
329+
extra_body: Add additional JSON properties to the request
330+
331+
timeout: Override the client-level default timeout for this request, in seconds
332+
"""
333+
body = deepcopy_minimal(
334+
{
335+
"file": file,
336+
"container_tags": container_tags,
337+
}
338+
)
339+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
340+
# It should be noted that the actual Content-Type header that will be
341+
# sent to the server will contain a `boundary` parameter, e.g.
342+
# multipart/form-data; boundary=---abc--
343+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
344+
return self._post(
345+
"/v3/memories/file",
346+
body=maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams),
347+
files=files,
348+
options=make_request_options(
349+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
350+
),
351+
cast_to=MemoryUploadFileResponse,
352+
)
353+
308354

309355
class AsyncMemoriesResource(AsyncAPIResource):
310356
@cached_property
@@ -584,6 +630,51 @@ async def get(
584630
cast_to=MemoryGetResponse,
585631
)
586632

633+
async def upload_file(
634+
self,
635+
*,
636+
file: FileTypes,
637+
container_tags: str | NotGiven = NOT_GIVEN,
638+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
639+
# The extra values given here take precedence over values defined on the client or passed to this method.
640+
extra_headers: Headers | None = None,
641+
extra_query: Query | None = None,
642+
extra_body: Body | None = None,
643+
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
644+
) -> MemoryUploadFileResponse:
645+
"""
646+
Upload a file to be processed
647+
648+
Args:
649+
extra_headers: Send extra headers
650+
651+
extra_query: Add additional query parameters to the request
652+
653+
extra_body: Add additional JSON properties to the request
654+
655+
timeout: Override the client-level default timeout for this request, in seconds
656+
"""
657+
body = deepcopy_minimal(
658+
{
659+
"file": file,
660+
"container_tags": container_tags,
661+
}
662+
)
663+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
664+
# It should be noted that the actual Content-Type header that will be
665+
# sent to the server will contain a `boundary` parameter, e.g.
666+
# multipart/form-data; boundary=---abc--
667+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
668+
return await self._post(
669+
"/v3/memories/file",
670+
body=await async_maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams),
671+
files=files,
672+
options=make_request_options(
673+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
674+
),
675+
cast_to=MemoryUploadFileResponse,
676+
)
677+
587678

588679
class MemoriesResourceWithRawResponse:
589680
def __init__(self, memories: MemoriesResource) -> None:
@@ -604,6 +695,9 @@ def __init__(self, memories: MemoriesResource) -> None:
604695
self.get = to_raw_response_wrapper(
605696
memories.get,
606697
)
698+
self.upload_file = to_raw_response_wrapper(
699+
memories.upload_file,
700+
)
607701

608702

609703
class AsyncMemoriesResourceWithRawResponse:
@@ -625,6 +719,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
625719
self.get = async_to_raw_response_wrapper(
626720
memories.get,
627721
)
722+
self.upload_file = async_to_raw_response_wrapper(
723+
memories.upload_file,
724+
)
628725

629726

630727
class MemoriesResourceWithStreamingResponse:
@@ -646,6 +743,9 @@ def __init__(self, memories: MemoriesResource) -> None:
646743
self.get = to_streamed_response_wrapper(
647744
memories.get,
648745
)
746+
self.upload_file = to_streamed_response_wrapper(
747+
memories.upload_file,
748+
)
649749

650750

651751
class AsyncMemoriesResourceWithStreamingResponse:
@@ -667,3 +767,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
667767
self.get = async_to_streamed_response_wrapper(
668768
memories.get,
669769
)
770+
self.upload_file = async_to_streamed_response_wrapper(
771+
memories.upload_file,
772+
)

src/supermemory/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams
1919
from .connection_import_params import ConnectionImportParams as ConnectionImportParams
2020
from .connection_list_response import ConnectionListResponse as ConnectionListResponse
21+
from .memory_upload_file_params import MemoryUploadFileParams as MemoryUploadFileParams
2122
from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse
23+
from .memory_upload_file_response import MemoryUploadFileResponse as MemoryUploadFileResponse
2224
from .connection_get_by_id_response import ConnectionGetByIDResponse as ConnectionGetByIDResponse
2325
from .connection_get_by_tags_params import ConnectionGetByTagsParams as ConnectionGetByTagsParams
2426
from .connection_get_by_tags_response import ConnectionGetByTagsResponse as ConnectionGetByTagsResponse
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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, Annotated, TypedDict
6+
7+
from .._types import FileTypes
8+
from .._utils import PropertyInfo
9+
10+
__all__ = ["MemoryUploadFileParams"]
11+
12+
13+
class MemoryUploadFileParams(TypedDict, total=False):
14+
file: Required[FileTypes]
15+
16+
container_tags: Annotated[str, PropertyInfo(alias="containerTags")]
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: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
MemoryGetResponse,
1515
MemoryListResponse,
1616
MemoryUpdateResponse,
17+
MemoryUploadFileResponse,
1718
)
1819

1920
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -254,6 +255,49 @@ def test_path_params_get(self, client: Supermemory) -> None:
254255
"",
255256
)
256257

258+
@pytest.mark.skip(reason="Prism tests are disabled")
259+
@parametrize
260+
def test_method_upload_file(self, client: Supermemory) -> None:
261+
memory = client.memories.upload_file(
262+
file=b"raw file contents",
263+
)
264+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
265+
266+
@pytest.mark.skip(reason="Prism tests are disabled")
267+
@parametrize
268+
def test_method_upload_file_with_all_params(self, client: Supermemory) -> None:
269+
memory = client.memories.upload_file(
270+
file=b"raw file contents",
271+
container_tags="containerTags",
272+
)
273+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
274+
275+
@pytest.mark.skip(reason="Prism tests are disabled")
276+
@parametrize
277+
def test_raw_response_upload_file(self, client: Supermemory) -> None:
278+
response = client.memories.with_raw_response.upload_file(
279+
file=b"raw file contents",
280+
)
281+
282+
assert response.is_closed is True
283+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
284+
memory = response.parse()
285+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
286+
287+
@pytest.mark.skip(reason="Prism tests are disabled")
288+
@parametrize
289+
def test_streaming_response_upload_file(self, client: Supermemory) -> None:
290+
with client.memories.with_streaming_response.upload_file(
291+
file=b"raw file contents",
292+
) as response:
293+
assert not response.is_closed
294+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
295+
296+
memory = response.parse()
297+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
298+
299+
assert cast(Any, response.is_closed) is True
300+
257301

258302
class TestAsyncMemories:
259303
parametrize = pytest.mark.parametrize(
@@ -491,3 +535,46 @@ async def test_path_params_get(self, async_client: AsyncSupermemory) -> None:
491535
await async_client.memories.with_raw_response.get(
492536
"",
493537
)
538+
539+
@pytest.mark.skip(reason="Prism tests are disabled")
540+
@parametrize
541+
async def test_method_upload_file(self, async_client: AsyncSupermemory) -> None:
542+
memory = await async_client.memories.upload_file(
543+
file=b"raw file contents",
544+
)
545+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
546+
547+
@pytest.mark.skip(reason="Prism tests are disabled")
548+
@parametrize
549+
async def test_method_upload_file_with_all_params(self, async_client: AsyncSupermemory) -> None:
550+
memory = await async_client.memories.upload_file(
551+
file=b"raw file contents",
552+
container_tags="containerTags",
553+
)
554+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
555+
556+
@pytest.mark.skip(reason="Prism tests are disabled")
557+
@parametrize
558+
async def test_raw_response_upload_file(self, async_client: AsyncSupermemory) -> None:
559+
response = await async_client.memories.with_raw_response.upload_file(
560+
file=b"raw file contents",
561+
)
562+
563+
assert response.is_closed is True
564+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
565+
memory = await response.parse()
566+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
567+
568+
@pytest.mark.skip(reason="Prism tests are disabled")
569+
@parametrize
570+
async def test_streaming_response_upload_file(self, async_client: AsyncSupermemory) -> None:
571+
async with async_client.memories.with_streaming_response.upload_file(
572+
file=b"raw file contents",
573+
) as response:
574+
assert not response.is_closed
575+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
576+
577+
memory = await response.parse()
578+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
579+
580+
assert cast(Any, response.is_closed) is True

0 commit comments

Comments
 (0)