Skip to content

Commit 3fd7de2

Browse files
feat(api): manual updates
1 parent c0d13e2 commit 3fd7de2

File tree

9 files changed

+212
-7
lines changed

9 files changed

+212
-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: 11
1+
configured_endpoints: 12
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-e6735b03c258b382c527550bb78042bdc3aad32a5cf564785dcb9f3fb13a2862.yml
33
openapi_spec_hash: 8168fb51314d986893554e1cc935ca7d
4-
config_hash: c8c1f2b0d63387d621f0cf066ae3379f
4+
config_hash: 8477e3ee6fd596ab6ac911d052e4de79

README.md

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

7878
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`.
7979

80+
## File uploads
81+
82+
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)`.
83+
84+
```python
85+
from pathlib import Path
86+
from supermemory import Supermemory
87+
88+
client = Supermemory()
89+
90+
client.memories.upload_file(
91+
file=Path("/path/to/file"),
92+
)
93+
```
94+
95+
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.
96+
8097
## Handling errors
8198

8299
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
@@ -9,6 +9,7 @@ from supermemory.types import (
99
MemoryDeleteResponse,
1010
MemoryAddResponse,
1111
MemoryGetResponse,
12+
MemoryUploadFileResponse,
1213
)
1314
```
1415

@@ -19,6 +20,7 @@ Methods:
1920
- <code title="delete /v3/memories/{id}">client.memories.<a href="./src/supermemory/resources/memories.py">delete</a>(id) -> <a href="./src/supermemory/types/memory_delete_response.py">MemoryDeleteResponse</a></code>
2021
- <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>
2122
- <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>
23+
- <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>
2224

2325
# Search
2426

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,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, 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, 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 (
@@ -24,6 +24,7 @@
2424
from ..types.memory_list_response import MemoryListResponse
2525
from ..types.memory_delete_response import MemoryDeleteResponse
2626
from ..types.memory_update_response import MemoryUpdateResponse
27+
from ..types.memory_upload_file_response import MemoryUploadFileResponse
2728

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

@@ -257,6 +258,45 @@ def get(
257258
cast_to=MemoryGetResponse,
258259
)
259260

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

261301
class AsyncMemoriesResource(AsyncAPIResource):
262302
@cached_property
@@ -487,6 +527,45 @@ async def get(
487527
cast_to=MemoryGetResponse,
488528
)
489529

530+
async def upload_file(
531+
self,
532+
*,
533+
file: FileTypes,
534+
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
535+
# The extra values given here take precedence over values defined on the client or passed to this method.
536+
extra_headers: Headers | None = None,
537+
extra_query: Query | None = None,
538+
extra_body: Body | None = None,
539+
timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
540+
) -> MemoryUploadFileResponse:
541+
"""
542+
Upload a file to be processed
543+
544+
Args:
545+
extra_headers: Send extra headers
546+
547+
extra_query: Add additional query parameters to the request
548+
549+
extra_body: Add additional JSON properties to the request
550+
551+
timeout: Override the client-level default timeout for this request, in seconds
552+
"""
553+
body = deepcopy_minimal({"file": file})
554+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
555+
# It should be noted that the actual Content-Type header that will be
556+
# sent to the server will contain a `boundary` parameter, e.g.
557+
# multipart/form-data; boundary=---abc--
558+
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
559+
return await self._post(
560+
"/v3/memories/file",
561+
body=await async_maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams),
562+
files=files,
563+
options=make_request_options(
564+
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
565+
),
566+
cast_to=MemoryUploadFileResponse,
567+
)
568+
490569

491570
class MemoriesResourceWithRawResponse:
492571
def __init__(self, memories: MemoriesResource) -> None:
@@ -507,6 +586,9 @@ def __init__(self, memories: MemoriesResource) -> None:
507586
self.get = to_raw_response_wrapper(
508587
memories.get,
509588
)
589+
self.upload_file = to_raw_response_wrapper(
590+
memories.upload_file,
591+
)
510592

511593

512594
class AsyncMemoriesResourceWithRawResponse:
@@ -528,6 +610,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
528610
self.get = async_to_raw_response_wrapper(
529611
memories.get,
530612
)
613+
self.upload_file = async_to_raw_response_wrapper(
614+
memories.upload_file,
615+
)
531616

532617

533618
class MemoriesResourceWithStreamingResponse:
@@ -549,6 +634,9 @@ def __init__(self, memories: MemoriesResource) -> None:
549634
self.get = to_streamed_response_wrapper(
550635
memories.get,
551636
)
637+
self.upload_file = to_streamed_response_wrapper(
638+
memories.upload_file,
639+
)
552640

553641

554642
class AsyncMemoriesResourceWithStreamingResponse:
@@ -570,3 +658,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
570658
self.get = async_to_streamed_response_wrapper(
571659
memories.get,
572660
)
661+
self.upload_file = async_to_streamed_response_wrapper(
662+
memories.upload_file,
663+
)

src/supermemory/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@
1919
from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse
2020
from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams
2121
from .connection_list_response import ConnectionListResponse as ConnectionListResponse
22+
from .memory_upload_file_params import MemoryUploadFileParams as MemoryUploadFileParams
2223
from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse
24+
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
@@ -15,6 +15,7 @@
1515
MemoryListResponse,
1616
MemoryDeleteResponse,
1717
MemoryUpdateResponse,
18+
MemoryUploadFileResponse,
1819
)
1920

2021
base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
@@ -262,6 +263,40 @@ def test_path_params_get(self, client: Supermemory) -> None:
262263
"",
263264
)
264265

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

266301
class TestAsyncMemories:
267302
parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
@@ -504,3 +539,37 @@ async def test_path_params_get(self, async_client: AsyncSupermemory) -> None:
504539
await async_client.memories.with_raw_response.get(
505540
"",
506541
)
542+
543+
@pytest.mark.skip()
544+
@parametrize
545+
async def test_method_upload_file(self, async_client: AsyncSupermemory) -> None:
546+
memory = await async_client.memories.upload_file(
547+
file=b"raw file contents",
548+
)
549+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
550+
551+
@pytest.mark.skip()
552+
@parametrize
553+
async def test_raw_response_upload_file(self, async_client: AsyncSupermemory) -> None:
554+
response = await async_client.memories.with_raw_response.upload_file(
555+
file=b"raw file contents",
556+
)
557+
558+
assert response.is_closed is True
559+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
560+
memory = await response.parse()
561+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
562+
563+
@pytest.mark.skip()
564+
@parametrize
565+
async def test_streaming_response_upload_file(self, async_client: AsyncSupermemory) -> None:
566+
async with async_client.memories.with_streaming_response.upload_file(
567+
file=b"raw file contents",
568+
) as response:
569+
assert not response.is_closed
570+
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
571+
572+
memory = await response.parse()
573+
assert_matches_type(MemoryUploadFileResponse, memory, path=["response"])
574+
575+
assert cast(Any, response.is_closed) is True

0 commit comments

Comments
 (0)