From 7a8e495ee6b8f162dce5c3124b007d2d60f6cdd6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 00:30:14 +0000 Subject: [PATCH 01/11] feat(api): api update --- .stats.yml | 4 ++-- src/hyperspell/resources/memories.py | 8 ++++++++ src/hyperspell/types/memory_update_params.py | 8 +++++++- tests/api_resources/test_memories.py | 2 ++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 60ac02f..73fab0a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-6d6dbb68dd9021348431b28e08378d086b3eaf5e65b3dfa03125b1fdec417fa6.yml -openapi_spec_hash: 6ad2b84ac07c482fe838929694e49015 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-864ec17bc07aef2b5985bc5e876c8a99a26c8f53cd0cee693deafbe752b6b7e1.yml +openapi_spec_hash: f1d9dbac709b8de23a5243f7ccaa984f config_hash: bd8505e17db740d82e578d0edaa9bfe0 diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py index d731f93..6f750b3 100644 --- a/src/hyperspell/resources/memories.py +++ b/src/hyperspell/resources/memories.py @@ -80,6 +80,7 @@ def update( "gmail_actions", ], collection: Union[str, object, None] | Omit = omit, + date: Union[Union[str, datetime], object, None] | Omit = omit, metadata: Union[Dict[str, Union[str, float, bool, None]], object, None] | Omit = omit, text: Union[str, object, None] | Omit = omit, title: Union[str, object, None] | Omit = omit, @@ -102,6 +103,8 @@ def update( collection: The collection to move the document to — deprecated, set the collection using metadata instead. + date: Date of the document for ranking and filtering. + metadata: Custom metadata for filtering. Keys must be alphanumeric with underscores, max 64 chars. Values must be string, number, boolean, or null. Will be merged with existing metadata. @@ -127,6 +130,7 @@ def update( body=maybe_transform( { "collection": collection, + "date": date, "metadata": metadata, "text": text, "title": title, @@ -647,6 +651,7 @@ async def update( "gmail_actions", ], collection: Union[str, object, None] | Omit = omit, + date: Union[Union[str, datetime], object, None] | Omit = omit, metadata: Union[Dict[str, Union[str, float, bool, None]], object, None] | Omit = omit, text: Union[str, object, None] | Omit = omit, title: Union[str, object, None] | Omit = omit, @@ -669,6 +674,8 @@ async def update( collection: The collection to move the document to — deprecated, set the collection using metadata instead. + date: Date of the document for ranking and filtering. + metadata: Custom metadata for filtering. Keys must be alphanumeric with underscores, max 64 chars. Values must be string, number, boolean, or null. Will be merged with existing metadata. @@ -694,6 +701,7 @@ async def update( body=await async_maybe_transform( { "collection": collection, + "date": date, "metadata": metadata, "text": text, "title": title, diff --git a/src/hyperspell/types/memory_update_params.py b/src/hyperspell/types/memory_update_params.py index d8859d6..c7a1938 100644 --- a/src/hyperspell/types/memory_update_params.py +++ b/src/hyperspell/types/memory_update_params.py @@ -3,7 +3,10 @@ from __future__ import annotations from typing import Dict, Union -from typing_extensions import Literal, Required, TypedDict +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo __all__ = ["MemoryUpdateParams"] @@ -34,6 +37,9 @@ class MemoryUpdateParams(TypedDict, total=False): metadata instead. """ + date: Annotated[Union[Union[str, datetime], object, None], PropertyInfo(format="iso8601")] + """Date of the document for ranking and filtering.""" + metadata: Union[Dict[str, Union[str, float, bool, None]], object, None] """Custom metadata for filtering. diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 2a983b3..b16232f 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -40,6 +40,7 @@ def test_method_update_with_all_params(self, client: Hyperspell) -> None: resource_id="resource_id", source="reddit", collection="string", + date=parse_datetime("2019-12-27T18:11:19.117Z"), metadata={"foo": "string"}, text="string", title="string", @@ -449,6 +450,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncHyperspell resource_id="resource_id", source="reddit", collection="string", + date=parse_datetime("2019-12-27T18:11:19.117Z"), metadata={"foo": "string"}, text="string", title="string", From c28640c64d25fd5dde07836e60e970069b0b65fe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:14:18 +0000 Subject: [PATCH 02/11] perf(client): optimize file structure copying in multipart requests --- src/hyperspell/_files.py | 56 +++++++++++++++- src/hyperspell/_utils/__init__.py | 1 - src/hyperspell/_utils/_utils.py | 15 ----- src/hyperspell/resources/memories.py | 13 ++-- tests/test_deepcopy.py | 58 ---------------- tests/test_files.py | 99 +++++++++++++++++++++++++++- 6 files changed, 159 insertions(+), 83 deletions(-) delete mode 100644 tests/test_deepcopy.py diff --git a/src/hyperspell/_files.py b/src/hyperspell/_files.py index 155adfe..34fa545 100644 --- a/src/hyperspell/_files.py +++ b/src/hyperspell/_files.py @@ -3,8 +3,8 @@ import io import os import pathlib -from typing import overload -from typing_extensions import TypeGuard +from typing import Sequence, cast, overload +from typing_extensions import TypeVar, TypeGuard import anyio @@ -17,7 +17,9 @@ HttpxFileContent, HttpxRequestFiles, ) -from ._utils import is_tuple_t, is_mapping_t, is_sequence_t +from ._utils import is_list, is_mapping, is_tuple_t, is_mapping_t, is_sequence_t + +_T = TypeVar("_T") def is_base64_file_input(obj: object) -> TypeGuard[Base64FileInput]: @@ -121,3 +123,51 @@ async def async_read_file_content(file: FileContent) -> HttpxFileContent: return await anyio.Path(file).read_bytes() return file + + +def deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]]) -> _T: + """Copy only the containers along the given paths. + + Used to guard against mutation by extract_files without copying the entire structure. + Only dicts and lists that lie on a path are copied; everything else + is returned by reference. + + For example, given paths=[["foo", "files", "file"]] and the structure: + { + "foo": { + "bar": {"baz": {}}, + "files": {"file": } + } + } + The root dict, "foo", and "files" are copied (they lie on the path). + "bar" and "baz" are returned by reference (off the path). + """ + return _deepcopy_with_paths(item, paths, 0) + + +def _deepcopy_with_paths(item: _T, paths: Sequence[Sequence[str]], index: int) -> _T: + if not paths: + return item + if is_mapping(item): + key_to_paths: dict[str, list[Sequence[str]]] = {} + for path in paths: + if index < len(path): + key_to_paths.setdefault(path[index], []).append(path) + + # if no path continues through this mapping, it won't be mutated and copying it is redundant + if not key_to_paths: + return item + + result = dict(item) + for key, subpaths in key_to_paths.items(): + if key in result: + result[key] = _deepcopy_with_paths(result[key], subpaths, index + 1) + return cast(_T, result) + if is_list(item): + array_paths = [path for path in paths if index < len(path) and path[index] == ""] + + # if no path expects a list here, nothing will be mutated inside it - return by reference + if not array_paths: + return cast(_T, item) + return cast(_T, [_deepcopy_with_paths(entry, array_paths, index + 1) for entry in item]) + return item diff --git a/src/hyperspell/_utils/__init__.py b/src/hyperspell/_utils/__init__.py index 10cb66d..1c090e5 100644 --- a/src/hyperspell/_utils/__init__.py +++ b/src/hyperspell/_utils/__init__.py @@ -24,7 +24,6 @@ coerce_integer as coerce_integer, file_from_path as file_from_path, strip_not_given as strip_not_given, - deepcopy_minimal as deepcopy_minimal, get_async_library as get_async_library, maybe_coerce_float as maybe_coerce_float, get_required_header as get_required_header, diff --git a/src/hyperspell/_utils/_utils.py b/src/hyperspell/_utils/_utils.py index 63b8cd6..771859f 100644 --- a/src/hyperspell/_utils/_utils.py +++ b/src/hyperspell/_utils/_utils.py @@ -177,21 +177,6 @@ def is_iterable(obj: object) -> TypeGuard[Iterable[object]]: return isinstance(obj, Iterable) -def deepcopy_minimal(item: _T) -> _T: - """Minimal reimplementation of copy.deepcopy() that will only copy certain object types: - - - mappings, e.g. `dict` - - list - - This is done for performance reasons. - """ - if is_mapping(item): - return cast(_T, {k: deepcopy_minimal(v) for k, v in item.items()}) - if is_list(item): - return cast(_T, [deepcopy_minimal(entry) for entry in item]) - return item - - # copied from https://github.com/Rapptz/RoboDanny def human_join(seq: Sequence[str], *, delim: str = ", ", final: str = "or") -> str: size = len(seq) diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py index 6f750b3..03a2875 100644 --- a/src/hyperspell/resources/memories.py +++ b/src/hyperspell/resources/memories.py @@ -16,8 +16,9 @@ memory_upload_params, memory_add_bulk_params, ) +from .._files import deepcopy_with_paths from .._types import Body, Omit, Query, Headers, NotGiven, FileTypes, omit, not_given -from .._utils import extract_files, path_template, maybe_transform, deepcopy_minimal, async_maybe_transform +from .._utils import extract_files, path_template, maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -587,12 +588,13 @@ def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "file": file, "collection": collection, "metadata": metadata, - } + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be @@ -1158,12 +1160,13 @@ async def upload( timeout: Override the client-level default timeout for this request, in seconds """ - body = deepcopy_minimal( + body = deepcopy_with_paths( { "file": file, "collection": collection, "metadata": metadata, - } + }, + [["file"]], ) files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) # It should be noted that the actual Content-Type header that will be diff --git a/tests/test_deepcopy.py b/tests/test_deepcopy.py deleted file mode 100644 index 93ca9c0..0000000 --- a/tests/test_deepcopy.py +++ /dev/null @@ -1,58 +0,0 @@ -from hyperspell._utils import deepcopy_minimal - - -def assert_different_identities(obj1: object, obj2: object) -> None: - assert obj1 == obj2 - assert id(obj1) != id(obj2) - - -def test_simple_dict() -> None: - obj1 = {"foo": "bar"} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_dict() -> None: - obj1 = {"foo": {"bar": True}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - - -def test_complex_nested_dict() -> None: - obj1 = {"foo": {"bar": [{"hello": "world"}]}} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1["foo"], obj2["foo"]) - assert_different_identities(obj1["foo"]["bar"], obj2["foo"]["bar"]) - assert_different_identities(obj1["foo"]["bar"][0], obj2["foo"]["bar"][0]) - - -def test_simple_list() -> None: - obj1 = ["a", "b", "c"] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - - -def test_nested_list() -> None: - obj1 = ["a", [1, 2, 3]] - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert_different_identities(obj1[1], obj2[1]) - - -class MyObject: ... - - -def test_ignores_other_types() -> None: - # custom classes - my_obj = MyObject() - obj1 = {"foo": my_obj} - obj2 = deepcopy_minimal(obj1) - assert_different_identities(obj1, obj2) - assert obj1["foo"] is my_obj - - # tuples - obj3 = ("a", "b") - obj4 = deepcopy_minimal(obj3) - assert obj3 is obj4 diff --git a/tests/test_files.py b/tests/test_files.py index aae00cb..e51c3d0 100644 --- a/tests/test_files.py +++ b/tests/test_files.py @@ -4,7 +4,8 @@ import pytest from dirty_equals import IsDict, IsList, IsBytes, IsTuple -from hyperspell._files import to_httpx_files, async_to_httpx_files +from hyperspell._files import to_httpx_files, deepcopy_with_paths, async_to_httpx_files +from hyperspell._utils import extract_files readme_path = Path(__file__).parent.parent.joinpath("README.md") @@ -49,3 +50,99 @@ def test_string_not_allowed() -> None: "file": "foo", # type: ignore } ) + + +def assert_different_identities(obj1: object, obj2: object) -> None: + assert obj1 == obj2 + assert obj1 is not obj2 + + +class TestDeepcopyWithPaths: + def test_copies_top_level_dict(self) -> None: + original = {"file": b"data", "other": "value"} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + + def test_file_value_is_same_reference(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes} + result = deepcopy_with_paths(original, [["file"]]) + assert_different_identities(result, original) + assert result["file"] is file_bytes + + def test_list_popped_wholesale(self) -> None: + files = [b"f1", b"f2"] + original = {"files": files, "title": "t"} + result = deepcopy_with_paths(original, [["files", ""]]) + assert_different_identities(result, original) + result_files = result["files"] + assert isinstance(result_files, list) + assert_different_identities(result_files, files) + + def test_nested_array_path_copies_list_and_elements(self) -> None: + elem1 = {"file": b"f1", "extra": 1} + elem2 = {"file": b"f2", "extra": 2} + original = {"items": [elem1, elem2]} + result = deepcopy_with_paths(original, [["items", "", "file"]]) + assert_different_identities(result, original) + result_items = result["items"] + assert isinstance(result_items, list) + assert_different_identities(result_items, original["items"]) + assert_different_identities(result_items[0], elem1) + assert_different_identities(result_items[1], elem2) + + def test_empty_paths_returns_same_object(self) -> None: + original = {"foo": "bar"} + result = deepcopy_with_paths(original, []) + assert result is original + + def test_multiple_paths(self) -> None: + f1 = b"file1" + f2 = b"file2" + original = {"a": f1, "b": f2, "c": "unchanged"} + result = deepcopy_with_paths(original, [["a"], ["b"]]) + assert_different_identities(result, original) + assert result["a"] is f1 + assert result["b"] is f2 + assert result["c"] is original["c"] + + def test_extract_files_does_not_mutate_original_top_level(self) -> None: + file_bytes = b"contents" + original = {"file": file_bytes, "other": "value"} + + copied = deepcopy_with_paths(original, [["file"]]) + extracted = extract_files(copied, paths=[["file"]]) + + assert extracted == [("file", file_bytes)] + assert original == {"file": file_bytes, "other": "value"} + assert copied == {"other": "value"} + + def test_extract_files_does_not_mutate_original_nested_array_path(self) -> None: + file1 = b"f1" + file2 = b"f2" + original = { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + + copied = deepcopy_with_paths(original, [["items", "", "file"]]) + extracted = extract_files(copied, paths=[["items", "", "file"]]) + + assert extracted == [("items[][file]", file1), ("items[][file]", file2)] + assert original == { + "items": [ + {"file": file1, "extra": 1}, + {"file": file2, "extra": 2}, + ], + "title": "example", + } + assert copied == { + "items": [ + {"extra": 1}, + {"extra": 2}, + ], + "title": "example", + } From 7d67d91802b13e3110af4e1ab313cc36ed66ca74 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 08:15:49 +0000 Subject: [PATCH 03/11] chore(tests): bump steady to v0.22.1 --- scripts/mock | 6 +++--- scripts/test | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/mock b/scripts/mock index 5cd7c15..feebe5e 100755 --- a/scripts/mock +++ b/scripts/mock @@ -22,9 +22,9 @@ echo "==> Starting mock server with URL ${URL}" # Run steady mock on the given spec if [ "$1" == "--daemon" ]; then # Pre-install the package so the download doesn't eat into the startup timeout - npm exec --package=@stdy/cli@0.20.2 -- steady --version + npm exec --package=@stdy/cli@0.22.1 -- steady --version - npm exec --package=@stdy/cli@0.20.2 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" &> .stdy.log & # Wait for server to come online via health endpoint (max 30s) echo -n "Waiting for server" @@ -48,5 +48,5 @@ if [ "$1" == "--daemon" ]; then echo else - npm exec --package=@stdy/cli@0.20.2 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" + npm exec --package=@stdy/cli@0.22.1 -- steady --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets "$URL" fi diff --git a/scripts/test b/scripts/test index b754ada..a47c5b4 100755 --- a/scripts/test +++ b/scripts/test @@ -43,7 +43,7 @@ elif ! steady_is_running ; then echo -e "To run the server, pass in the path or url of your OpenAPI" echo -e "spec to the steady command:" echo - echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.20.2 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" + echo -e " \$ ${YELLOW}npm exec --package=@stdy/cli@0.22.1 -- steady path/to/your.openapi.yml --host 127.0.0.1 -p 4010 --validator-query-array-format=comma --validator-form-array-format=comma --validator-query-object-format=brackets --validator-form-object-format=brackets${NC}" echo exit 1 From f50d5a32152fd0a33ad5634bc72e03a6332bc293 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 18:30:28 +0000 Subject: [PATCH 04/11] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 73fab0a..19377f4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-864ec17bc07aef2b5985bc5e876c8a99a26c8f53cd0cee693deafbe752b6b7e1.yml -openapi_spec_hash: f1d9dbac709b8de23a5243f7ccaa984f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a4bf4f3aaf1509541db646bc9ff7ec58e78cb4d42cf6bf83881b02739ad77b87.yml +openapi_spec_hash: 89cd02b2290061877e6badcddb7c8eb8 config_hash: bd8505e17db740d82e578d0edaa9bfe0 From 384db7ac2a8a40d8d4e72aaebf7a5c67d2a15ac2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:20:35 +0000 Subject: [PATCH 05/11] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 19377f4..b925885 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a4bf4f3aaf1509541db646bc9ff7ec58e78cb4d42cf6bf83881b02739ad77b87.yml openapi_spec_hash: 89cd02b2290061877e6badcddb7c8eb8 -config_hash: bd8505e17db740d82e578d0edaa9bfe0 +config_hash: 2ac853a71ed8b421da86d06bd90cf23f From 3c63a3bb5093eb0cc3fe44ac30979ab006c95bb0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:26:40 +0000 Subject: [PATCH 06/11] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index b925885..390cd28 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a4bf4f3aaf1509541db646bc9ff7ec58e78cb4d42cf6bf83881b02739ad77b87.yml openapi_spec_hash: 89cd02b2290061877e6badcddb7c8eb8 -config_hash: 2ac853a71ed8b421da86d06bd90cf23f +config_hash: f779e7db9263cd21efe5e9469bc1d012 From 23c9221674f7d4d4d563b3e8d80d55c0b884114a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:38:43 +0000 Subject: [PATCH 07/11] feat(api): manual updates --- .stats.yml | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 390cd28..08bee8b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a4bf4f3aaf1509541db646bc9ff7ec58e78cb4d42cf6bf83881b02739ad77b87.yml openapi_spec_hash: 89cd02b2290061877e6badcddb7c8eb8 -config_hash: f779e7db9263cd21efe5e9469bc1d012 +config_hash: 597eba5e5eaec83a5f0db3d946af8db5 diff --git a/README.md b/README.md index 4b0ec92..da027ae 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,8 @@ It is generated with [Stainless](https://www.stainless.com/). Use the Hyperspell 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. -[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=hyperspell-mcp&config=eyJuYW1lIjoiaHlwZXJzcGVsbC1tY3AiLCJ0cmFuc3BvcnQiOiJodHRwIiwidXJsIjoiaHR0cHM6Ly9oeXBlcnNwZWxsLnN0bG1jcC5jb20iLCJoZWFkZXJzIjp7IngtaHlwZXJzcGVsbC1hcGkta2V5IjoiTXkgQVBJIEtleSIsIlgtQXMtVXNlciI6Ik15IFVzZXIgSUQifX0) -[![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%22hyperspell-mcp%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fhyperspell.stlmcp.com%22%2C%22headers%22%3A%7B%22x-hyperspell-api-key%22%3A%22My%20API%20Key%22%2C%22X-As-User%22%3A%22My%20User%20ID%22%7D%7D) +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=%40hyperspell%2Fhyperspell-mcp&config=eyJuYW1lIjoiQGh5cGVyc3BlbGwvaHlwZXJzcGVsbC1tY3AiLCJ0cmFuc3BvcnQiOiJodHRwIiwidXJsIjoiaHR0cHM6Ly9oeXBlcnNwZWxsLnN0bG1jcC5jb20iLCJoZWFkZXJzIjp7IngtaHlwZXJzcGVsbC1hcGkta2V5IjoiTXkgQVBJIEtleSIsIlgtQXMtVXNlciI6Ik15IFVzZXIgSUQifX0) +[![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%40hyperspell%2Fhyperspell-mcp%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fhyperspell.stlmcp.com%22%2C%22headers%22%3A%7B%22x-hyperspell-api-key%22%3A%22My%20API%20Key%22%2C%22X-As-User%22%3A%22My%20User%20ID%22%7D%7D) > Note: You may need to set environment variables in your MCP client. From e5611df610fefaf3ed0cf98f7f53cb8aded5c8a6 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:51:59 +0000 Subject: [PATCH 08/11] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 08bee8b..dba5310 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a4bf4f3aaf1509541db646bc9ff7ec58e78cb4d42cf6bf83881b02739ad77b87.yml openapi_spec_hash: 89cd02b2290061877e6badcddb7c8eb8 -config_hash: 597eba5e5eaec83a5f0db3d946af8db5 +config_hash: 09bb5ca4418f316f95d2b75ef7399cf0 From 9be7d849f4c646fd81ef78f7efe908ebd734de8d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 22 Apr 2026 23:12:19 +0000 Subject: [PATCH 09/11] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index dba5310..08bee8b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 30 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-a4bf4f3aaf1509541db646bc9ff7ec58e78cb4d42cf6bf83881b02739ad77b87.yml openapi_spec_hash: 89cd02b2290061877e6badcddb7c8eb8 -config_hash: 09bb5ca4418f316f95d2b75ef7399cf0 +config_hash: 597eba5e5eaec83a5f0db3d946af8db5 From d4a39dc3c6f710bf9b9c481197043eeb4a9f8ac9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 04:05:58 +0000 Subject: [PATCH 10/11] chore(internal): more robust bootstrap script --- scripts/bootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bootstrap b/scripts/bootstrap index 4638ec6..5a23841 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,7 +4,7 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "${SKIP_BREW:-}" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { echo -n "==> Install Homebrew dependencies? (y/N): " read -r response From 5e40e1e25c5336574144611ad0feda0e0226e85e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 04:06:21 +0000 Subject: [PATCH 11/11] release: 0.38.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ pyproject.toml | 2 +- src/hyperspell/_version.py | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 51acdaa..8ea07c9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.37.0" + ".": "0.38.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d769a..f5e26e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 0.38.0 (2026-04-23) + +Full Changelog: [v0.37.0...v0.38.0](https://github.com/hyperspell/python-sdk/compare/v0.37.0...v0.38.0) + +### Features + +* **api:** api update ([7a8e495](https://github.com/hyperspell/python-sdk/commit/7a8e495ee6b8f162dce5c3124b007d2d60f6cdd6)) +* **api:** manual updates ([23c9221](https://github.com/hyperspell/python-sdk/commit/23c9221674f7d4d4d563b3e8d80d55c0b884114a)) + + +### Performance Improvements + +* **client:** optimize file structure copying in multipart requests ([c28640c](https://github.com/hyperspell/python-sdk/commit/c28640c64d25fd5dde07836e60e970069b0b65fe)) + + +### Chores + +* **internal:** more robust bootstrap script ([d4a39dc](https://github.com/hyperspell/python-sdk/commit/d4a39dc3c6f710bf9b9c481197043eeb4a9f8ac9)) +* **tests:** bump steady to v0.22.1 ([7d67d91](https://github.com/hyperspell/python-sdk/commit/7d67d91802b13e3110af4e1ab313cc36ed66ca74)) + ## 0.37.0 (2026-04-16) Full Changelog: [v0.36.0...v0.37.0](https://github.com/hyperspell/python-sdk/compare/v0.36.0...v0.37.0) diff --git a/pyproject.toml b/pyproject.toml index 7a6fa07..13682bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hyperspell" -version = "0.37.0" +version = "0.38.0" description = "The official Python library for the hyperspell API" dynamic = ["readme"] license = "MIT" diff --git a/src/hyperspell/_version.py b/src/hyperspell/_version.py index ea4e056..db685ee 100644 --- a/src/hyperspell/_version.py +++ b/src/hyperspell/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "hyperspell" -__version__ = "0.37.0" # x-release-please-version +__version__ = "0.38.0" # x-release-please-version