From 0789efa8c1e2762ce8bf5329a7411dba886709dd Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Mon, 21 Jul 2025 10:31:44 +0000
Subject: [PATCH 01/12] feat(api): api update
---
.stats.yml | 4 ++--
src/hyperspell/types/memory.py | 4 ++++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 2c00fa68..089d40f8 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 12
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-4bfa5ab6a0021f526d6f6f622f1872f2ec3467b6d1bd51acfc17bfc6f399f915.yml
-openapi_spec_hash: 5b51df5010bab70a1f3f884552ce25c3
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-c5a312fc607285c8d4e1a6354f1c4f7aa751640926325767c9e1e25c0e0ef982.yml
+openapi_spec_hash: 17ebbedf0c8f6e996ba8b30e392a2f57
config_hash: c894437241b21cedd2d01854f1c7a8ef
diff --git a/src/hyperspell/types/memory.py b/src/hyperspell/types/memory.py
index 0b9b3fe7..f62822a3 100644
--- a/src/hyperspell/types/memory.py
+++ b/src/hyperspell/types/memory.py
@@ -18,6 +18,8 @@ class MetadataEvent(BaseModel):
class Metadata(BaseModel):
+ created_at: Optional[datetime] = None
+
events: Optional[List[MetadataEvent]] = None
indexed_at: Optional[datetime] = None
@@ -26,6 +28,8 @@ class Metadata(BaseModel):
status: Optional[Literal["pending", "processing", "completed", "failed"]] = None
+ url: Optional[str] = None
+
if TYPE_CHECKING:
# Stub to indicate that arbitrary properties are accepted.
# To access properties that are not valid identifiers you can use `getattr`, e.g.
From 01e34f8e8697e5380884a0d93dd6b2d248f578d0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Tue, 22 Jul 2025 02:25:01 +0000
Subject: [PATCH 02/12] fix(parsing): ignore empty metadata
---
src/hyperspell/_models.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hyperspell/_models.py b/src/hyperspell/_models.py
index 528d5680..ffcbf67b 100644
--- a/src/hyperspell/_models.py
+++ b/src/hyperspell/_models.py
@@ -439,7 +439,7 @@ def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]
type_ = type_.__value__ # type: ignore[unreachable]
# unwrap `Annotated[T, ...]` -> `T`
- if metadata is not None:
+ if metadata is not None and len(metadata) > 0:
meta: tuple[Any, ...] = tuple(metadata)
elif is_annotated_type(type_):
meta = get_args(type_)[1:]
From 167331c90f0001fa4165d2cafa185c6017383dad Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Wed, 23 Jul 2025 02:27:21 +0000
Subject: [PATCH 03/12] fix(parsing): parse extra field types
---
src/hyperspell/_models.py | 25 +++++++++++++++++++++++--
src/hyperspell/types/memory.py | 5 ++++-
tests/test_models.py | 29 ++++++++++++++++++++++++++++-
3 files changed, 55 insertions(+), 4 deletions(-)
diff --git a/src/hyperspell/_models.py b/src/hyperspell/_models.py
index ffcbf67b..b8387ce9 100644
--- a/src/hyperspell/_models.py
+++ b/src/hyperspell/_models.py
@@ -208,14 +208,18 @@ def construct( # pyright: ignore[reportIncompatibleMethodOverride]
else:
fields_values[name] = field_get_default(field)
+ extra_field_type = _get_extra_fields_type(__cls)
+
_extra = {}
for key, value in values.items():
if key not in model_fields:
+ parsed = construct_type(value=value, type_=extra_field_type) if extra_field_type is not None else value
+
if PYDANTIC_V2:
- _extra[key] = value
+ _extra[key] = parsed
else:
_fields_set.add(key)
- fields_values[key] = value
+ fields_values[key] = parsed
object.__setattr__(m, "__dict__", fields_values)
@@ -370,6 +374,23 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object:
return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None))
+def _get_extra_fields_type(cls: type[pydantic.BaseModel]) -> type | None:
+ if not PYDANTIC_V2:
+ # TODO
+ return None
+
+ schema = cls.__pydantic_core_schema__
+ if schema["type"] == "model":
+ fields = schema["schema"]
+ if fields["type"] == "model-fields":
+ extras = fields.get("extras_schema")
+ if extras and "cls" in extras:
+ # mypy can't narrow the type
+ return extras["cls"] # type: ignore[no-any-return]
+
+ return None
+
+
def is_basemodel(type_: type) -> bool:
"""Returns whether or not the given type is either a `BaseModel` or a union of `BaseModel`"""
if is_union(type_):
diff --git a/src/hyperspell/types/memory.py b/src/hyperspell/types/memory.py
index f62822a3..021ba64d 100644
--- a/src/hyperspell/types/memory.py
+++ b/src/hyperspell/types/memory.py
@@ -1,9 +1,11 @@
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
-from typing import TYPE_CHECKING, List, Optional
+from typing import TYPE_CHECKING, Dict, List, Optional
from datetime import datetime
from typing_extensions import Literal
+from pydantic import Field as FieldInfo
+
from .._models import BaseModel
__all__ = ["Memory", "Metadata", "MetadataEvent"]
@@ -30,6 +32,7 @@ class Metadata(BaseModel):
url: Optional[str] = None
+ __pydantic_extra__: Dict[str, object] = FieldInfo(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
if TYPE_CHECKING:
# Stub to indicate that arbitrary properties are accepted.
# To access properties that are not valid identifiers you can use `getattr`, e.g.
diff --git a/tests/test_models.py b/tests/test_models.py
index cfcb602e..0a875386 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,5 +1,5 @@
import json
-from typing import Any, Dict, List, Union, Optional, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional, cast
from datetime import datetime, timezone
from typing_extensions import Literal, Annotated, TypeAliasType
@@ -934,3 +934,30 @@ class Type2(BaseModel):
)
assert isinstance(model, Type1)
assert isinstance(model.value, InnerType2)
+
+
+@pytest.mark.skipif(not PYDANTIC_V2, reason="this is only supported in pydantic v2 for now")
+def test_extra_properties() -> None:
+ class Item(BaseModel):
+ prop: int
+
+ class Model(BaseModel):
+ __pydantic_extra__: Dict[str, Item] = Field(init=False) # pyright: ignore[reportIncompatibleVariableOverride]
+
+ other: str
+
+ if TYPE_CHECKING:
+
+ def __getattr__(self, attr: str) -> Item: ...
+
+ model = construct_type(
+ type_=Model,
+ value={
+ "a": {"prop": 1},
+ "other": "foo",
+ },
+ )
+ assert isinstance(model, Model)
+ assert model.a.prop == 1
+ assert isinstance(model.a, Item)
+ assert model.other == "foo"
From 899e127f80b783c0d8381e5df5db09b344a2c10f Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 24 Jul 2025 05:31:34 +0000
Subject: [PATCH 04/12] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 089d40f8..de59d8c5 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 12
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-c5a312fc607285c8d4e1a6354f1c4f7aa751640926325767c9e1e25c0e0ef982.yml
-openapi_spec_hash: 17ebbedf0c8f6e996ba8b30e392a2f57
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-f368b082c2c82c2327960da5f9cd3448f5362a3e3bf967e97ef52f495387992c.yml
+openapi_spec_hash: b29d86bd558c1273348049af59bb9bf1
config_hash: c894437241b21cedd2d01854f1c7a8ef
From f597f9542a2572674dfc0a97fd94950cee114edf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 25 Jul 2025 06:04:58 +0000
Subject: [PATCH 05/12] chore(project): add settings file for vscode
---
.gitignore | 1 -
.vscode/settings.json | 3 +++
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 .vscode/settings.json
diff --git a/.gitignore b/.gitignore
index 87797408..95ceb189 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,4 @@
.prism.log
-.vscode
_dev
__pycache__
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..5b010307
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "python.analysis.importFormat": "relative",
+}
From 98a9c26c10636926fb05844b3494707e5d64babf Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 08:30:56 +0000
Subject: [PATCH 06/12] feat(client): support file upload requests
---
src/hyperspell/_base_client.py | 5 ++++-
src/hyperspell/_files.py | 8 ++++----
2 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/hyperspell/_base_client.py b/src/hyperspell/_base_client.py
index 93ac9ef9..9ff21427 100644
--- a/src/hyperspell/_base_client.py
+++ b/src/hyperspell/_base_client.py
@@ -532,7 +532,10 @@ def _build_request(
is_body_allowed = options.method.lower() != "get"
if is_body_allowed:
- kwargs["json"] = json_data if is_given(json_data) else None
+ if isinstance(json_data, bytes):
+ kwargs["content"] = json_data
+ else:
+ kwargs["json"] = json_data if is_given(json_data) else None
kwargs["files"] = files
else:
headers.pop("Content-Type", None)
diff --git a/src/hyperspell/_files.py b/src/hyperspell/_files.py
index 1d0a3358..155adfec 100644
--- a/src/hyperspell/_files.py
+++ b/src/hyperspell/_files.py
@@ -69,12 +69,12 @@ def _transform_file(file: FileTypes) -> HttpxFileTypes:
return file
if is_tuple_t(file):
- return (file[0], _read_file_content(file[1]), *file[2:])
+ return (file[0], read_file_content(file[1]), *file[2:])
raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
-def _read_file_content(file: FileContent) -> HttpxFileContent:
+def read_file_content(file: FileContent) -> HttpxFileContent:
if isinstance(file, os.PathLike):
return pathlib.Path(file).read_bytes()
return file
@@ -111,12 +111,12 @@ async def _async_transform_file(file: FileTypes) -> HttpxFileTypes:
return file
if is_tuple_t(file):
- return (file[0], await _async_read_file_content(file[1]), *file[2:])
+ return (file[0], await async_read_file_content(file[1]), *file[2:])
raise TypeError(f"Expected file types input to be a FileContent type or to be a tuple")
-async def _async_read_file_content(file: FileContent) -> HttpxFileContent:
+async def async_read_file_content(file: FileContent) -> HttpxFileContent:
if isinstance(file, os.PathLike):
return await anyio.Path(file).read_bytes()
From 6b411be3d85dbc3106741cfbfa08454ce443b173 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 19:31:59 +0000
Subject: [PATCH 07/12] feat(api): api update
---
.stats.yml | 4 +-
README.md | 4 +-
src/hyperspell/resources/memories.py | 8 -
src/hyperspell/types/memory_search_params.py | 212 +++++++++++--------
tests/api_resources/test_memories.py | 148 ++++++++-----
5 files changed, 217 insertions(+), 159 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index de59d8c5..361a87b5 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 12
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-f368b082c2c82c2327960da5f9cd3448f5362a3e3bf967e97ef52f495387992c.yml
-openapi_spec_hash: b29d86bd558c1273348049af59bb9bf1
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-998f41a32297ed65e4e05c201a5922cc48c51dc15e709ba7914a25e963c2473e.yml
+openapi_spec_hash: 3f349c4cfbb749537e01a370e75330f8
config_hash: c894437241b21cedd2d01854f1c7a8ef
diff --git a/README.md b/README.md
index 5890113e..9a5f7b4a 100644
--- a/README.md
+++ b/README.md
@@ -193,9 +193,9 @@ client = Hyperspell()
response = client.memories.search(
query="query",
- filter={},
+ options={},
)
-print(response.filter)
+print(response.options)
```
## File uploads
diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py
index 94ed512e..5af4f4c1 100644
--- a/src/hyperspell/resources/memories.py
+++ b/src/hyperspell/resources/memories.py
@@ -302,7 +302,6 @@ def search(
*,
query: str,
answer: bool | NotGiven = NOT_GIVEN,
- filter: Optional[memory_search_params.Filter] | NotGiven = NOT_GIVEN,
max_results: int | NotGiven = NOT_GIVEN,
options: memory_search_params.Options | NotGiven = NOT_GIVEN,
sources: List[
@@ -371,8 +370,6 @@ def search(
answer: If true, the query will be answered along with matching source documents.
- filter: DEPRECATED: Use options instead. This field will be removed in a future version.
-
max_results: Maximum number of results to return.
options: Search options for the query.
@@ -393,7 +390,6 @@ def search(
{
"query": query,
"answer": answer,
- "filter": filter,
"max_results": max_results,
"options": options,
"sources": sources,
@@ -755,7 +751,6 @@ async def search(
*,
query: str,
answer: bool | NotGiven = NOT_GIVEN,
- filter: Optional[memory_search_params.Filter] | NotGiven = NOT_GIVEN,
max_results: int | NotGiven = NOT_GIVEN,
options: memory_search_params.Options | NotGiven = NOT_GIVEN,
sources: List[
@@ -824,8 +819,6 @@ async def search(
answer: If true, the query will be answered along with matching source documents.
- filter: DEPRECATED: Use options instead. This field will be removed in a future version.
-
max_results: Maximum number of results to return.
options: Search options for the query.
@@ -846,7 +839,6 @@ async def search(
{
"query": query,
"answer": answer,
- "filter": filter,
"max_results": max_results,
"options": options,
"sources": sources,
diff --git a/src/hyperspell/types/memory_search_params.py b/src/hyperspell/types/memory_search_params.py
index 9f97bc11..8c3940cb 100644
--- a/src/hyperspell/types/memory_search_params.py
+++ b/src/hyperspell/types/memory_search_params.py
@@ -10,15 +10,11 @@
__all__ = [
"MemorySearchParams",
- "Filter",
- "FilterGoogleCalendar",
- "FilterGoogleMail",
- "FilterNotion",
- "FilterReddit",
- "FilterSlack",
- "FilterWebCrawler",
"Options",
+ "OptionsBox",
+ "OptionsCollections",
"OptionsGoogleCalendar",
+ "OptionsGoogleDrive",
"OptionsGoogleMail",
"OptionsNotion",
"OptionsReddit",
@@ -34,12 +30,6 @@ class MemorySearchParams(TypedDict, total=False):
answer: bool
"""If true, the query will be answered along with matching source documents."""
- filter: Optional[Filter]
- """DEPRECATED: Use options instead.
-
- This field will be removed in a future version.
- """
-
max_results: int
"""Maximum number of results to return."""
@@ -99,111 +89,84 @@ class MemorySearchParams(TypedDict, total=False):
"""Only query documents from these sources."""
-class FilterGoogleCalendar(TypedDict, total=False):
- calendar_id: Optional[str]
- """The ID of the calendar to search.
-
- If not provided, it will use the ID of the default calendar. You can get the
- list of calendars with the `/integrations/google_calendar/list` endpoint.
- """
-
-
-class FilterGoogleMail(TypedDict, total=False):
- label_ids: List[str]
- """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']).
-
- Multiple labels are combined with OR logic - messages matching ANY specified
- label will be returned. If empty, no label filtering is applied (searches all
- accessible messages).
- """
+class OptionsBox(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
-class FilterNotion(TypedDict, total=False):
- notion_page_ids: List[str]
- """List of Notion page IDs to search.
+ weight: float
+ """Weight of results from this source.
- If not provided, all pages in the workspace will be searched.
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
"""
-class FilterReddit(TypedDict, total=False):
- period: Literal["hour", "day", "week", "month", "year", "all"]
- """The time period to search. Defaults to 'month'."""
-
- sort: Literal["relevance", "new", "hot", "top", "comments"]
- """The sort order of the posts. Defaults to 'relevance'."""
-
- subreddit: Optional[str]
- """The subreddit to search.
-
- If not provided, the query will be searched for in all subreddits.
- """
+class OptionsCollections(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
-class FilterSlack(TypedDict, total=False):
- channels: List[str]
- """List of Slack channels to search.
+ weight: float
+ """Weight of results from this source.
- If not provided, all channels in the workspace will be searched.
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
"""
-class FilterWebCrawler(TypedDict, total=False):
- max_depth: int
- """Maximum depth to crawl from the starting URL"""
-
- url: Union[str, object]
- """The URL to crawl"""
-
-
-class Filter(TypedDict, total=False):
+class OptionsGoogleCalendar(TypedDict, total=False):
after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
"""Only query documents created on or after this date."""
- answer_model: Literal["llama-3.1", "gemma2", "qwen-qwq", "mistral-saba", "llama-4-scout", "deepseek-r1"]
- """Model to use for answer generation when answer=True"""
-
before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
"""Only query documents created before this date."""
- box: object
- """Search options for Box"""
-
- collections: object
- """Search options for vault"""
-
- google_calendar: FilterGoogleCalendar
- """Search options for Google Calendar"""
-
- google_drive: object
- """Search options for Google Drive"""
+ calendar_id: Optional[str]
+ """The ID of the calendar to search.
- google_mail: FilterGoogleMail
- """Search options for Gmail"""
+ If not provided, it will use the ID of the default calendar. You can get the
+ list of calendars with the `/integrations/google_calendar/list` endpoint.
+ """
- notion: FilterNotion
- """Search options for Notion"""
+ weight: float
+ """Weight of results from this source.
- reddit: FilterReddit
- """Search options for Reddit"""
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
+ """
- slack: FilterSlack
- """Search options for Slack"""
- web_crawler: FilterWebCrawler
- """Search options for Web Crawler"""
+class OptionsGoogleDrive(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
-class OptionsGoogleCalendar(TypedDict, total=False):
- calendar_id: Optional[str]
- """The ID of the calendar to search.
+ weight: float
+ """Weight of results from this source.
- If not provided, it will use the ID of the default calendar. You can get the
- list of calendars with the `/integrations/google_calendar/list` endpoint.
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
"""
class OptionsGoogleMail(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
+
label_ids: List[str]
"""List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']).
@@ -212,16 +175,44 @@ class OptionsGoogleMail(TypedDict, total=False):
accessible messages).
"""
+ weight: float
+ """Weight of results from this source.
+
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
+ """
+
class OptionsNotion(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
+
notion_page_ids: List[str]
"""List of Notion page IDs to search.
If not provided, all pages in the workspace will be searched.
"""
+ weight: float
+ """Weight of results from this source.
+
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
+ """
+
class OptionsReddit(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
+
period: Literal["hour", "day", "week", "month", "year", "all"]
"""The time period to search. Defaults to 'month'."""
@@ -234,22 +225,58 @@ class OptionsReddit(TypedDict, total=False):
If not provided, the query will be searched for in all subreddits.
"""
+ weight: float
+ """Weight of results from this source.
+
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
+ """
+
class OptionsSlack(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
+
channels: List[str]
"""List of Slack channels to search.
If not provided, all channels in the workspace will be searched.
"""
+ weight: float
+ """Weight of results from this source.
+
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
+ """
+
class OptionsWebCrawler(TypedDict, total=False):
+ after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created on or after this date."""
+
+ before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
+ """Only query documents created before this date."""
+
max_depth: int
"""Maximum depth to crawl from the starting URL"""
url: Union[str, object]
"""The URL to crawl"""
+ weight: float
+ """Weight of results from this source.
+
+ A weight greater than 1.0 means more results from this source will be returned,
+ a weight less than 1.0 means fewer results will be returned. This will only
+ affect results if multiple sources are queried at the same time.
+ """
+
class Options(TypedDict, total=False):
after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
@@ -261,21 +288,24 @@ class Options(TypedDict, total=False):
before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")]
"""Only query documents created before this date."""
- box: object
+ box: OptionsBox
"""Search options for Box"""
- collections: object
+ collections: OptionsCollections
"""Search options for vault"""
google_calendar: OptionsGoogleCalendar
"""Search options for Google Calendar"""
- google_drive: object
+ google_drive: OptionsGoogleDrive
"""Search options for Google Drive"""
google_mail: OptionsGoogleMail
"""Search options for Gmail"""
+ max_results: int
+ """Maximum number of results to return."""
+
notion: OptionsNotion
"""Search options for Notion"""
diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py
index b08ba4a7..6240ab96 100644
--- a/tests/api_resources/test_memories.py
+++ b/tests/api_resources/test_memories.py
@@ -155,47 +155,65 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None:
memory = client.memories.search(
query="query",
answer=True,
- filter={
- "after": parse_datetime("2019-12-27T18:11:19.117Z"),
- "answer_model": "llama-3.1",
- "before": parse_datetime("2019-12-27T18:11:19.117Z"),
- "box": {},
- "collections": {},
- "google_calendar": {"calendar_id": "calendar_id"},
- "google_drive": {},
- "google_mail": {"label_ids": ["string"]},
- "notion": {"notion_page_ids": ["string"]},
- "reddit": {
- "period": "hour",
- "sort": "relevance",
- "subreddit": "subreddit",
- },
- "slack": {"channels": ["string"]},
- "web_crawler": {
- "max_depth": 0,
- "url": "string",
- },
- },
max_results=0,
options={
"after": parse_datetime("2019-12-27T18:11:19.117Z"),
"answer_model": "llama-3.1",
"before": parse_datetime("2019-12-27T18:11:19.117Z"),
- "box": {},
- "collections": {},
- "google_calendar": {"calendar_id": "calendar_id"},
- "google_drive": {},
- "google_mail": {"label_ids": ["string"]},
- "notion": {"notion_page_ids": ["string"]},
+ "box": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "weight": 0,
+ },
+ "collections": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "weight": 0,
+ },
+ "google_calendar": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "calendar_id": "calendar_id",
+ "weight": 0,
+ },
+ "google_drive": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "weight": 0,
+ },
+ "google_mail": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "label_ids": ["string"],
+ "weight": 0,
+ },
+ "max_results": 0,
+ "notion": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "notion_page_ids": ["string"],
+ "weight": 0,
+ },
"reddit": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
"period": "hour",
"sort": "relevance",
"subreddit": "subreddit",
+ "weight": 0,
+ },
+ "slack": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "channels": ["string"],
+ "weight": 0,
},
- "slack": {"channels": ["string"]},
"web_crawler": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
"max_depth": 0,
"url": "string",
+ "weight": 0,
},
},
sources=["collections"],
@@ -427,47 +445,65 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell
memory = await async_client.memories.search(
query="query",
answer=True,
- filter={
- "after": parse_datetime("2019-12-27T18:11:19.117Z"),
- "answer_model": "llama-3.1",
- "before": parse_datetime("2019-12-27T18:11:19.117Z"),
- "box": {},
- "collections": {},
- "google_calendar": {"calendar_id": "calendar_id"},
- "google_drive": {},
- "google_mail": {"label_ids": ["string"]},
- "notion": {"notion_page_ids": ["string"]},
- "reddit": {
- "period": "hour",
- "sort": "relevance",
- "subreddit": "subreddit",
- },
- "slack": {"channels": ["string"]},
- "web_crawler": {
- "max_depth": 0,
- "url": "string",
- },
- },
max_results=0,
options={
"after": parse_datetime("2019-12-27T18:11:19.117Z"),
"answer_model": "llama-3.1",
"before": parse_datetime("2019-12-27T18:11:19.117Z"),
- "box": {},
- "collections": {},
- "google_calendar": {"calendar_id": "calendar_id"},
- "google_drive": {},
- "google_mail": {"label_ids": ["string"]},
- "notion": {"notion_page_ids": ["string"]},
+ "box": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "weight": 0,
+ },
+ "collections": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "weight": 0,
+ },
+ "google_calendar": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "calendar_id": "calendar_id",
+ "weight": 0,
+ },
+ "google_drive": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "weight": 0,
+ },
+ "google_mail": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "label_ids": ["string"],
+ "weight": 0,
+ },
+ "max_results": 0,
+ "notion": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "notion_page_ids": ["string"],
+ "weight": 0,
+ },
"reddit": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
"period": "hour",
"sort": "relevance",
"subreddit": "subreddit",
+ "weight": 0,
+ },
+ "slack": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "channels": ["string"],
+ "weight": 0,
},
- "slack": {"channels": ["string"]},
"web_crawler": {
+ "after": parse_datetime("2019-12-27T18:11:19.117Z"),
+ "before": parse_datetime("2019-12-27T18:11:19.117Z"),
"max_depth": 0,
"url": "string",
+ "weight": 0,
},
},
sources=["collections"],
From a92de5a8dbc7c6b516b58d159840657c356cf423 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 19:39:26 +0000
Subject: [PATCH 08/12] feat(api): update via SDK Studio
---
.stats.yml | 4 +-
api.md | 9 +-
src/hyperspell/resources/memories.py | 207 ++++++++++++++++++
src/hyperspell/types/__init__.py | 1 +
.../types/memory_delete_response.py | 66 ++++++
tests/api_resources/test_memories.py | 85 +++++++
6 files changed, 369 insertions(+), 3 deletions(-)
create mode 100644 src/hyperspell/types/memory_delete_response.py
diff --git a/.stats.yml b/.stats.yml
index 361a87b5..01b9020a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 12
+configured_endpoints: 13
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-998f41a32297ed65e4e05c201a5922cc48c51dc15e709ba7914a25e963c2473e.yml
openapi_spec_hash: 3f349c4cfbb749537e01a370e75330f8
-config_hash: c894437241b21cedd2d01854f1c7a8ef
+config_hash: 1431271962db6749c2be9ee6b2027ced
diff --git a/api.md b/api.md
index bba49d1a..f59feeb3 100644
--- a/api.md
+++ b/api.md
@@ -39,12 +39,19 @@ Methods:
Types:
```python
-from hyperspell.types import Memory, MemoryStatus, MemorySearchResponse, MemoryStatusResponse
+from hyperspell.types import (
+ Memory,
+ MemoryStatus,
+ MemoryDeleteResponse,
+ MemorySearchResponse,
+ MemoryStatusResponse,
+)
```
Methods:
- client.memories.list(\*\*params) -> SyncCursorPage[Memory]
+- client.memories.delete(resource_id, \*, source) -> MemoryDeleteResponse
- client.memories.add(\*\*params) -> MemoryStatus
- client.memories.get(resource_id, \*, source) -> Memory
- client.memories.search(\*\*params) -> MemorySearchResponse
diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py
index 5af4f4c1..14082a27 100644
--- a/src/hyperspell/resources/memories.py
+++ b/src/hyperspell/resources/memories.py
@@ -23,6 +23,7 @@
from .._base_client import AsyncPaginator, make_request_options
from ..types.memory import Memory
from ..types.memory_status import MemoryStatus
+from ..types.memory_delete_response import MemoryDeleteResponse
from ..types.memory_search_response import MemorySearchResponse
from ..types.memory_status_response import MemoryStatusResponse
@@ -152,6 +153,103 @@ def list(
model=Memory,
)
+ def delete(
+ self,
+ resource_id: str,
+ *,
+ source: Literal[
+ "collections",
+ "vault",
+ "web_crawler",
+ "notion",
+ "slack",
+ "google_calendar",
+ "reddit",
+ "box",
+ "google_drive",
+ "airtable",
+ "algolia",
+ "amplitude",
+ "asana",
+ "ashby",
+ "bamboohr",
+ "basecamp",
+ "bubbles",
+ "calendly",
+ "confluence",
+ "clickup",
+ "datadog",
+ "deel",
+ "discord",
+ "dropbox",
+ "exa",
+ "facebook",
+ "front",
+ "github",
+ "gitlab",
+ "google_docs",
+ "google_mail",
+ "google_sheet",
+ "hubspot",
+ "jira",
+ "linear",
+ "microsoft_teams",
+ "mixpanel",
+ "monday",
+ "outlook",
+ "perplexity",
+ "rippling",
+ "salesforce",
+ "segment",
+ "todoist",
+ "twitter",
+ "zoom",
+ ],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> MemoryDeleteResponse:
+ """
+ Delete a memory and its associated chunks from the index.
+
+ This removes the memory completely from the vector index and database. The
+ operation deletes:
+
+ 1. All chunks associated with the resource (including embeddings)
+ 2. The resource record itself
+
+ Args: source: The document provider (e.g., gmail, notion, vault) resource_id:
+ The unique identifier of the resource to delete api_token: Authentication token
+
+ Returns: MemoryDeletionResponse with deletion details
+
+ Raises: DocumentNotFound: If the resource doesn't exist or user doesn't have
+ access
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not source:
+ raise ValueError(f"Expected a non-empty value for `source` but received {source!r}")
+ if not resource_id:
+ raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
+ return self._delete(
+ f"/memories/delete/{source}/{resource_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=MemoryDeleteResponse,
+ )
+
def add(
self,
*,
@@ -601,6 +699,103 @@ def list(
model=Memory,
)
+ async def delete(
+ self,
+ resource_id: str,
+ *,
+ source: Literal[
+ "collections",
+ "vault",
+ "web_crawler",
+ "notion",
+ "slack",
+ "google_calendar",
+ "reddit",
+ "box",
+ "google_drive",
+ "airtable",
+ "algolia",
+ "amplitude",
+ "asana",
+ "ashby",
+ "bamboohr",
+ "basecamp",
+ "bubbles",
+ "calendly",
+ "confluence",
+ "clickup",
+ "datadog",
+ "deel",
+ "discord",
+ "dropbox",
+ "exa",
+ "facebook",
+ "front",
+ "github",
+ "gitlab",
+ "google_docs",
+ "google_mail",
+ "google_sheet",
+ "hubspot",
+ "jira",
+ "linear",
+ "microsoft_teams",
+ "mixpanel",
+ "monday",
+ "outlook",
+ "perplexity",
+ "rippling",
+ "salesforce",
+ "segment",
+ "todoist",
+ "twitter",
+ "zoom",
+ ],
+ # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
+ # The extra values given here take precedence over values defined on the client or passed to this method.
+ extra_headers: Headers | None = None,
+ extra_query: Query | None = None,
+ extra_body: Body | None = None,
+ timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
+ ) -> MemoryDeleteResponse:
+ """
+ Delete a memory and its associated chunks from the index.
+
+ This removes the memory completely from the vector index and database. The
+ operation deletes:
+
+ 1. All chunks associated with the resource (including embeddings)
+ 2. The resource record itself
+
+ Args: source: The document provider (e.g., gmail, notion, vault) resource_id:
+ The unique identifier of the resource to delete api_token: Authentication token
+
+ Returns: MemoryDeletionResponse with deletion details
+
+ Raises: DocumentNotFound: If the resource doesn't exist or user doesn't have
+ access
+
+ Args:
+ extra_headers: Send extra headers
+
+ extra_query: Add additional query parameters to the request
+
+ extra_body: Add additional JSON properties to the request
+
+ timeout: Override the client-level default timeout for this request, in seconds
+ """
+ if not source:
+ raise ValueError(f"Expected a non-empty value for `source` but received {source!r}")
+ if not resource_id:
+ raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}")
+ return await self._delete(
+ f"/memories/delete/{source}/{resource_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=MemoryDeleteResponse,
+ )
+
async def add(
self,
*,
@@ -934,6 +1129,9 @@ def __init__(self, memories: MemoriesResource) -> None:
self.list = to_raw_response_wrapper(
memories.list,
)
+ self.delete = to_raw_response_wrapper(
+ memories.delete,
+ )
self.add = to_raw_response_wrapper(
memories.add,
)
@@ -958,6 +1156,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
self.list = async_to_raw_response_wrapper(
memories.list,
)
+ self.delete = async_to_raw_response_wrapper(
+ memories.delete,
+ )
self.add = async_to_raw_response_wrapper(
memories.add,
)
@@ -982,6 +1183,9 @@ def __init__(self, memories: MemoriesResource) -> None:
self.list = to_streamed_response_wrapper(
memories.list,
)
+ self.delete = to_streamed_response_wrapper(
+ memories.delete,
+ )
self.add = to_streamed_response_wrapper(
memories.add,
)
@@ -1006,6 +1210,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None:
self.list = async_to_streamed_response_wrapper(
memories.list,
)
+ self.delete = async_to_streamed_response_wrapper(
+ memories.delete,
+ )
self.add = async_to_streamed_response_wrapper(
memories.add,
)
diff --git a/src/hyperspell/types/__init__.py b/src/hyperspell/types/__init__.py
index bfaf01aa..1cb2bdab 100644
--- a/src/hyperspell/types/__init__.py
+++ b/src/hyperspell/types/__init__.py
@@ -13,6 +13,7 @@
from .memory_search_params import MemorySearchParams as MemorySearchParams
from .memory_upload_params import MemoryUploadParams as MemoryUploadParams
from .auth_user_token_params import AuthUserTokenParams as AuthUserTokenParams
+from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse
from .memory_search_response import MemorySearchResponse as MemorySearchResponse
from .memory_status_response import MemoryStatusResponse as MemoryStatusResponse
from .integration_revoke_response import IntegrationRevokeResponse as IntegrationRevokeResponse
diff --git a/src/hyperspell/types/memory_delete_response.py b/src/hyperspell/types/memory_delete_response.py
new file mode 100644
index 00000000..7f5fbd62
--- /dev/null
+++ b/src/hyperspell/types/memory_delete_response.py
@@ -0,0 +1,66 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing_extensions import Literal
+
+from .._models import BaseModel
+
+__all__ = ["MemoryDeleteResponse"]
+
+
+class MemoryDeleteResponse(BaseModel):
+ chunks_deleted: int
+
+ message: str
+
+ resource_id: str
+
+ source: Literal[
+ "collections",
+ "vault",
+ "web_crawler",
+ "notion",
+ "slack",
+ "google_calendar",
+ "reddit",
+ "box",
+ "google_drive",
+ "airtable",
+ "algolia",
+ "amplitude",
+ "asana",
+ "ashby",
+ "bamboohr",
+ "basecamp",
+ "bubbles",
+ "calendly",
+ "confluence",
+ "clickup",
+ "datadog",
+ "deel",
+ "discord",
+ "dropbox",
+ "exa",
+ "facebook",
+ "front",
+ "github",
+ "gitlab",
+ "google_docs",
+ "google_mail",
+ "google_sheet",
+ "hubspot",
+ "jira",
+ "linear",
+ "microsoft_teams",
+ "mixpanel",
+ "monday",
+ "outlook",
+ "perplexity",
+ "rippling",
+ "salesforce",
+ "segment",
+ "todoist",
+ "twitter",
+ "zoom",
+ ]
+
+ success: bool
diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py
index 6240ab96..cd3041d8 100644
--- a/tests/api_resources/test_memories.py
+++ b/tests/api_resources/test_memories.py
@@ -12,6 +12,7 @@
from hyperspell.types import (
Memory,
MemoryStatus,
+ MemoryDeleteResponse,
MemorySearchResponse,
MemoryStatusResponse,
)
@@ -59,6 +60,48 @@ def test_streaming_response_list(self, client: Hyperspell) -> None:
assert cast(Any, response.is_closed) is True
+ @parametrize
+ def test_method_delete(self, client: Hyperspell) -> None:
+ memory = client.memories.delete(
+ resource_id="resource_id",
+ source="collections",
+ )
+ assert_matches_type(MemoryDeleteResponse, memory, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Hyperspell) -> None:
+ response = client.memories.with_raw_response.delete(
+ resource_id="resource_id",
+ source="collections",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ memory = response.parse()
+ assert_matches_type(MemoryDeleteResponse, memory, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Hyperspell) -> None:
+ with client.memories.with_streaming_response.delete(
+ resource_id="resource_id",
+ source="collections",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ memory = response.parse()
+ assert_matches_type(MemoryDeleteResponse, memory, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Hyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"):
+ client.memories.with_raw_response.delete(
+ resource_id="",
+ source="collections",
+ )
+
@parametrize
def test_method_add(self, client: Hyperspell) -> None:
memory = client.memories.add(
@@ -349,6 +392,48 @@ async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> N
assert cast(Any, response.is_closed) is True
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncHyperspell) -> None:
+ memory = await async_client.memories.delete(
+ resource_id="resource_id",
+ source="collections",
+ )
+ assert_matches_type(MemoryDeleteResponse, memory, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncHyperspell) -> None:
+ response = await async_client.memories.with_raw_response.delete(
+ resource_id="resource_id",
+ source="collections",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ memory = await response.parse()
+ assert_matches_type(MemoryDeleteResponse, memory, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncHyperspell) -> None:
+ async with async_client.memories.with_streaming_response.delete(
+ resource_id="resource_id",
+ source="collections",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ memory = await response.parse()
+ assert_matches_type(MemoryDeleteResponse, memory, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncHyperspell) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"):
+ await async_client.memories.with_raw_response.delete(
+ resource_id="",
+ source="collections",
+ )
+
@parametrize
async def test_method_add(self, async_client: AsyncHyperspell) -> None:
memory = await async_client.memories.add(
From 294ab731dbeb12356b179fe47424dd750fc5d962 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Thu, 31 Jul 2025 19:54:59 +0000
Subject: [PATCH 09/12] codegen metadata
---
.stats.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 01b9020a..8b41ddff 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 13
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-998f41a32297ed65e4e05c201a5922cc48c51dc15e709ba7914a25e963c2473e.yml
-openapi_spec_hash: 3f349c4cfbb749537e01a370e75330f8
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e8dc461bfd68745a68eddc289ac15c0c5d222f20e81a5c61b11ee3807a75d839.yml
+openapi_spec_hash: 24eece3c79152b96c2cc0f398cf9d6af
config_hash: 1431271962db6749c2be9ee6b2027ced
From afa2a6e593b3b8059916ed9cfd4b3ddd2f4334c0 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 09:38:41 +0000
Subject: [PATCH 10/12] codegen metadata
---
.stats.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.stats.yml b/.stats.yml
index 8b41ddff..6e1620f2 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 13
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e8dc461bfd68745a68eddc289ac15c0c5d222f20e81a5c61b11ee3807a75d839.yml
openapi_spec_hash: 24eece3c79152b96c2cc0f398cf9d6af
-config_hash: 1431271962db6749c2be9ee6b2027ced
+config_hash: 424d553df98a1fa5e35523b464d3a85e
From fd10b3b4b26774c5fcd033481712fda2cb77cc80 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 09:39:11 +0000
Subject: [PATCH 11/12] feat(api): update via SDK Studio
---
.stats.yml | 2 +-
LICENSE | 202 +------------------------------------------------
pyproject.toml | 4 +-
3 files changed, 7 insertions(+), 201 deletions(-)
diff --git a/.stats.yml b/.stats.yml
index 6e1620f2..70f6684a 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 13
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e8dc461bfd68745a68eddc289ac15c0c5d222f20e81a5c61b11ee3807a75d839.yml
openapi_spec_hash: 24eece3c79152b96c2cc0f398cf9d6af
-config_hash: 424d553df98a1fa5e35523b464d3a85e
+config_hash: bf6196b98ec72829d458bc45ccd5a5f9
diff --git a/LICENSE b/LICENSE
index 85e0f735..9239282a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,201 +1,7 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
+Copyright 2025 hyperspell
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- 1. Definitions.
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2025 Hyperspell
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/pyproject.toml b/pyproject.toml
index b08762c7..d8ce4685 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ name = "hyperspell"
version = "0.19.0"
description = "The official Python library for the hyperspell API"
dynamic = ["readme"]
-license = "Apache-2.0"
+license = "MIT"
authors = [
{ name = "Hyperspell", email = "hello@hyperspell.com" },
]
@@ -31,7 +31,7 @@ classifiers = [
"Operating System :: POSIX :: Linux",
"Operating System :: Microsoft :: Windows",
"Topic :: Software Development :: Libraries :: Python Modules",
- "License :: OSI Approved :: Apache Software License"
+ "License :: OSI Approved :: MIT License"
]
[project.urls]
From 716e43edc26f9c22d327767360e766a8710d91c1 Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 1 Aug 2025 09:39:29 +0000
Subject: [PATCH 12/12] release: 0.20.0
---
.release-please-manifest.json | 2 +-
CHANGELOG.md | 23 +++++++++++++++++++++++
pyproject.toml | 2 +-
src/hyperspell/_version.py | 2 +-
4 files changed, 26 insertions(+), 3 deletions(-)
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index e7562934..0c2ecec6 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.19.0"
+ ".": "0.20.0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e3ee8045..fe1e190f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,28 @@
# Changelog
+## 0.20.0 (2025-08-01)
+
+Full Changelog: [v0.19.0...v0.20.0](https://github.com/hyperspell/python-sdk/compare/v0.19.0...v0.20.0)
+
+### Features
+
+* **api:** api update ([6b411be](https://github.com/hyperspell/python-sdk/commit/6b411be3d85dbc3106741cfbfa08454ce443b173))
+* **api:** api update ([0789efa](https://github.com/hyperspell/python-sdk/commit/0789efa8c1e2762ce8bf5329a7411dba886709dd))
+* **api:** update via SDK Studio ([fd10b3b](https://github.com/hyperspell/python-sdk/commit/fd10b3b4b26774c5fcd033481712fda2cb77cc80))
+* **api:** update via SDK Studio ([a92de5a](https://github.com/hyperspell/python-sdk/commit/a92de5a8dbc7c6b516b58d159840657c356cf423))
+* **client:** support file upload requests ([98a9c26](https://github.com/hyperspell/python-sdk/commit/98a9c26c10636926fb05844b3494707e5d64babf))
+
+
+### Bug Fixes
+
+* **parsing:** ignore empty metadata ([01e34f8](https://github.com/hyperspell/python-sdk/commit/01e34f8e8697e5380884a0d93dd6b2d248f578d0))
+* **parsing:** parse extra field types ([167331c](https://github.com/hyperspell/python-sdk/commit/167331c90f0001fa4165d2cafa185c6017383dad))
+
+
+### Chores
+
+* **project:** add settings file for vscode ([f597f95](https://github.com/hyperspell/python-sdk/commit/f597f9542a2572674dfc0a97fd94950cee114edf))
+
## 0.19.0 (2025-07-18)
Full Changelog: [v0.18.0...v0.19.0](https://github.com/hyperspell/python-sdk/compare/v0.18.0...v0.19.0)
diff --git a/pyproject.toml b/pyproject.toml
index d8ce4685..3a9b3f08 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "hyperspell"
-version = "0.19.0"
+version = "0.20.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 60454a0d..b381fc89 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.19.0" # x-release-please-version
+__version__ = "0.20.0" # x-release-please-version