Skip to content

Commit 7db075c

Browse files
feat(api): api update
1 parent 4865c08 commit 7db075c

6 files changed

Lines changed: 56 additions & 33 deletions

File tree

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 30
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a73d280435ed6084d8ac9d9d7feb235de6141d866d40725ed26a27b87b0cf364.yml
3-
openapi_spec_hash: 91eabc37804d07ce801b1d4ea1778d1c
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-7d29d0843a52840291678a3c6d136f496ae1f956853abaa5003c1284ca2c94aa.yml
3+
openapi_spec_hash: 010597ad0ec6376fbf2f01ec062787ad
44
config_hash: bd8505e17db740d82e578d0edaa9bfe0

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,23 @@ query_result = client.memories.search(
200200
print(query_result.options)
201201
```
202202

203+
## File uploads
204+
205+
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)`.
206+
207+
```python
208+
from pathlib import Path
209+
from hyperspell import Hyperspell
210+
211+
client = Hyperspell()
212+
213+
client.memories.upload(
214+
file=Path("/path/to/file"),
215+
)
216+
```
217+
218+
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.
219+
203220
## Handling errors
204221

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

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

4040

src/hyperspell/resources/memories.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

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

@@ -16,8 +16,8 @@
1616
memory_upload_params,
1717
memory_add_bulk_params,
1818
)
19-
from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given
20-
from .._utils import path_template, maybe_transform, async_maybe_transform
19+
from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given
20+
from .._utils import extract_files, path_template, maybe_transform, deepcopy_minimal, async_maybe_transform
2121
from .._compat import cached_property
2222
from .._resource import SyncAPIResource, AsyncAPIResource
2323
from .._response import (
@@ -549,7 +549,7 @@ def status(
549549
def upload(
550550
self,
551551
*,
552-
file: str,
552+
file: FileTypes,
553553
collection: Optional[str] | Omit = omit,
554554
metadata: Optional[str] | Omit = omit,
555555
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -583,20 +583,22 @@ def upload(
583583
584584
timeout: Override the client-level default timeout for this request, in seconds
585585
"""
586+
body = deepcopy_minimal(
587+
{
588+
"file": file,
589+
"collection": collection,
590+
"metadata": metadata,
591+
}
592+
)
593+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
586594
# It should be noted that the actual Content-Type header that will be
587595
# sent to the server will contain a `boundary` parameter, e.g.
588596
# multipart/form-data; boundary=---abc--
589597
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
590598
return self._post(
591599
"/memories/upload",
592-
body=maybe_transform(
593-
{
594-
"file": file,
595-
"collection": collection,
596-
"metadata": metadata,
597-
},
598-
memory_upload_params.MemoryUploadParams,
599-
),
600+
body=maybe_transform(body, memory_upload_params.MemoryUploadParams),
601+
files=files,
600602
options=make_request_options(
601603
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
602604
),
@@ -1114,7 +1116,7 @@ async def status(
11141116
async def upload(
11151117
self,
11161118
*,
1117-
file: str,
1119+
file: FileTypes,
11181120
collection: Optional[str] | Omit = omit,
11191121
metadata: Optional[str] | Omit = omit,
11201122
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
@@ -1148,20 +1150,22 @@ async def upload(
11481150
11491151
timeout: Override the client-level default timeout for this request, in seconds
11501152
"""
1153+
body = deepcopy_minimal(
1154+
{
1155+
"file": file,
1156+
"collection": collection,
1157+
"metadata": metadata,
1158+
}
1159+
)
1160+
files = extract_files(cast(Mapping[str, object], body), paths=[["file"]])
11511161
# It should be noted that the actual Content-Type header that will be
11521162
# sent to the server will contain a `boundary` parameter, e.g.
11531163
# multipart/form-data; boundary=---abc--
11541164
extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})}
11551165
return await self._post(
11561166
"/memories/upload",
1157-
body=await async_maybe_transform(
1158-
{
1159-
"file": file,
1160-
"collection": collection,
1161-
"metadata": metadata,
1162-
},
1163-
memory_upload_params.MemoryUploadParams,
1164-
),
1167+
body=await async_maybe_transform(body, memory_upload_params.MemoryUploadParams),
1168+
files=files,
11651169
options=make_request_options(
11661170
extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
11671171
),

src/hyperspell/types/memory_upload_params.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
from typing import Optional
66
from typing_extensions import Required, TypedDict
77

8+
from .._types import FileTypes
9+
810
__all__ = ["MemoryUploadParams"]
911

1012

1113
class MemoryUploadParams(TypedDict, total=False):
12-
file: Required[str]
14+
file: Required[FileTypes]
1315
"""The file to ingest."""
1416

1517
collection: Optional[str]

tests/api_resources/test_memories.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -388,14 +388,14 @@ def test_streaming_response_status(self, client: Hyperspell) -> None:
388388
@parametrize
389389
def test_method_upload(self, client: Hyperspell) -> None:
390390
memory = client.memories.upload(
391-
file="file",
391+
file=b"Example data",
392392
)
393393
assert_matches_type(MemoryStatus, memory, path=["response"])
394394

395395
@parametrize
396396
def test_method_upload_with_all_params(self, client: Hyperspell) -> None:
397397
memory = client.memories.upload(
398-
file="file",
398+
file=b"Example data",
399399
collection="collection",
400400
metadata="metadata",
401401
)
@@ -404,7 +404,7 @@ def test_method_upload_with_all_params(self, client: Hyperspell) -> None:
404404
@parametrize
405405
def test_raw_response_upload(self, client: Hyperspell) -> None:
406406
response = client.memories.with_raw_response.upload(
407-
file="file",
407+
file=b"Example data",
408408
)
409409

410410
assert response.is_closed is True
@@ -415,7 +415,7 @@ def test_raw_response_upload(self, client: Hyperspell) -> None:
415415
@parametrize
416416
def test_streaming_response_upload(self, client: Hyperspell) -> None:
417417
with client.memories.with_streaming_response.upload(
418-
file="file",
418+
file=b"Example data",
419419
) as response:
420420
assert not response.is_closed
421421
assert response.http_request.headers.get("X-Stainless-Lang") == "python"
@@ -793,14 +793,14 @@ async def test_streaming_response_status(self, async_client: AsyncHyperspell) ->
793793
@parametrize
794794
async def test_method_upload(self, async_client: AsyncHyperspell) -> None:
795795
memory = await async_client.memories.upload(
796-
file="file",
796+
file=b"Example data",
797797
)
798798
assert_matches_type(MemoryStatus, memory, path=["response"])
799799

800800
@parametrize
801801
async def test_method_upload_with_all_params(self, async_client: AsyncHyperspell) -> None:
802802
memory = await async_client.memories.upload(
803-
file="file",
803+
file=b"Example data",
804804
collection="collection",
805805
metadata="metadata",
806806
)
@@ -809,7 +809,7 @@ async def test_method_upload_with_all_params(self, async_client: AsyncHyperspell
809809
@parametrize
810810
async def test_raw_response_upload(self, async_client: AsyncHyperspell) -> None:
811811
response = await async_client.memories.with_raw_response.upload(
812-
file="file",
812+
file=b"Example data",
813813
)
814814

815815
assert response.is_closed is True
@@ -820,7 +820,7 @@ async def test_raw_response_upload(self, async_client: AsyncHyperspell) -> None:
820820
@parametrize
821821
async def test_streaming_response_upload(self, async_client: AsyncHyperspell) -> None:
822822
async with async_client.memories.with_streaming_response.upload(
823-
file="file",
823+
file=b"Example data",
824824
) as response:
825825
assert not response.is_closed
826826
assert response.http_request.headers.get("X-Stainless-Lang") == "python"

0 commit comments

Comments
 (0)