From 2403f1da4d83266ddf49ada0103c8f5d432bf966 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 29 Apr 2025 19:16:34 +0000 Subject: [PATCH 01/24] feat(api): api update --- .stats.yml | 4 ++-- src/supermemory/types/memory_list_response.py | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/.stats.yml b/.stats.yml index b07d08ce..8f081055 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-f7a84a68f7d3173627cb589cec436ed7e89c98b2c3e66bbf42549da7346f3560.yml -openapi_spec_hash: 40877051c2167db483e240b4226d840f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-17320f0488a9b933245c77e962932c0323d8e940123f2571f93e476c5b5c2d18.yml +openapi_spec_hash: 8c13c404e5eb19f455dc7155c7f93e45 config_hash: eb32087403f958eead829e810f5a71b8 diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index b5490f7f..7e5a7ed2 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -36,11 +36,6 @@ class Memory(BaseModel): url: Optional[str] = None """Source URL of the memory""" - workflow_status: Optional[Literal["PENDING", "IN_PROGRESS", "COMPLETED", "FAILED"]] = FieldInfo( - alias="workflowStatus", default=None - ) - """Current workflow status""" - class Pagination(BaseModel): current_page: float = FieldInfo(alias="currentPage") From fda6f9f111dac7db4bba42779e967356b8615e3c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 00:16:35 +0000 Subject: [PATCH 02/24] feat(api): api update --- .stats.yml | 4 ++-- README.md | 27 +++++++++++++++------------ src/supermemory/_client.py | 12 ------------ tests/test_client.py | 22 +--------------------- 4 files changed, 18 insertions(+), 47 deletions(-) diff --git a/.stats.yml b/.stats.yml index 8f081055..befa4f9e 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-17320f0488a9b933245c77e962932c0323d8e940123f2571f93e476c5b5c2d18.yml -openapi_spec_hash: 8c13c404e5eb19f455dc7155c7f93e45 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-aebc3d012c87df9d5cb4d651a374fceefb5154e74c70f51d927c76b923267a85.yml +openapi_spec_hash: 98493a823305edac2c711cd052971f3d config_hash: eb32087403f958eead829e810f5a71b8 diff --git a/README.md b/README.md index 3dfd7e8c..bc02d4c0 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,10 @@ pip install --pre supermemory The full API of this library can be found in [api.md](api.md). ```python -import os from supermemory import Supermemory client = Supermemory( - api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted + api_key="My API Key", ) response = client.search.execute( @@ -37,22 +36,16 @@ response = client.search.execute( print(response.results) ``` -While you can provide an `api_key` keyword argument, -we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) -to add `SUPERMEMORY_API_KEY="My API Key"` to your `.env` file -so that your API Key is not stored in source control. - ## Async usage Simply import `AsyncSupermemory` instead of `Supermemory` and use `await` with each API call: ```python -import os import asyncio from supermemory import AsyncSupermemory client = AsyncSupermemory( - api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted + api_key="My API Key", ) @@ -90,7 +83,9 @@ All errors inherit from `supermemory.APIError`. import supermemory from supermemory import Supermemory -client = Supermemory() +client = Supermemory( + api_key="My API Key", +) try: client.memory.create( @@ -133,6 +128,7 @@ from supermemory import Supermemory # Configure the default for all requests: client = Supermemory( + api_key="My API Key", # default is 2 max_retries=0, ) @@ -153,12 +149,14 @@ from supermemory import Supermemory # Configure the default for all requests: client = Supermemory( + api_key="My API Key", # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: client = Supermemory( + api_key="My API Key", timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) @@ -205,7 +203,9 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to ```py from supermemory import Supermemory -client = Supermemory() +client = Supermemory( + api_key="My API Key", +) response = client.memory.with_raw_response.create( content="This is a detailed article about machine learning concepts...", ) @@ -284,6 +284,7 @@ import httpx from supermemory import Supermemory, DefaultHttpxClient client = Supermemory( + api_key="My API Key", # Or use the `SUPERMEMORY_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( @@ -306,7 +307,9 @@ By default the library closes underlying HTTP connections whenever the client is ```py from supermemory import Supermemory -with Supermemory() as client: +with Supermemory( + api_key="My API Key", +) as client: # make requests here ... diff --git a/src/supermemory/_client.py b/src/supermemory/_client.py index 10d8d620..266329fc 100644 --- a/src/supermemory/_client.py +++ b/src/supermemory/_client.py @@ -116,12 +116,6 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - api_key = self.api_key - return {"X-API-Key": api_key} - @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -290,12 +284,6 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") - @property - @override - def auth_headers(self) -> dict[str, str]: - api_key = self.api_key - return {"X-API-Key": api_key} - @property @override def default_headers(self) -> dict[str, str | Omit]: diff --git a/tests/test_client.py b/tests/test_client.py index b625c8c7..30dc5897 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -26,7 +26,7 @@ from supermemory._utils import maybe_transform from supermemory._models import BaseModel, FinalRequestOptions from supermemory._constants import RAW_RESPONSE_HEADER -from supermemory._exceptions import APIStatusError, APITimeoutError, SupermemoryError, APIResponseValidationError +from supermemory._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError from supermemory._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, @@ -336,16 +336,6 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" - def test_validate_headers(self) -> None: - client = Supermemory(base_url=base_url, api_key=api_key, _strict_response_validation=True) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("X-API-Key") == api_key - - with pytest.raises(SupermemoryError): - with update_env(**{"SUPERMEMORY_API_KEY": Omit()}): - client2 = Supermemory(base_url=base_url, api_key=None, _strict_response_validation=True) - _ = client2 - def test_default_query_option(self) -> None: client = Supermemory( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} @@ -1128,16 +1118,6 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" - def test_validate_headers(self) -> None: - client = AsyncSupermemory(base_url=base_url, api_key=api_key, _strict_response_validation=True) - request = client._build_request(FinalRequestOptions(method="get", url="/foo")) - assert request.headers.get("X-API-Key") == api_key - - with pytest.raises(SupermemoryError): - with update_env(**{"SUPERMEMORY_API_KEY": Omit()}): - client2 = AsyncSupermemory(base_url=base_url, api_key=None, _strict_response_validation=True) - _ = client2 - def test_default_query_option(self) -> None: client = AsyncSupermemory( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} From 844d56ecd40cffb441c47050e5e820051d65af7e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 02:16:36 +0000 Subject: [PATCH 03/24] feat(api): api update --- .stats.yml | 4 ++-- src/supermemory/resources/search.py | 10 ++++++++++ src/supermemory/types/memory_list_response.py | 3 +++ src/supermemory/types/search_execute_params.py | 6 ++++++ tests/api_resources/test_search.py | 2 ++ 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index befa4f9e..c3fa7868 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-aebc3d012c87df9d5cb4d651a374fceefb5154e74c70f51d927c76b923267a85.yml -openapi_spec_hash: 98493a823305edac2c711cd052971f3d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-b581efe40ff877fc21a621d2e1b9419bf8126142f14f818416f69f6ffa376ddb.yml +openapi_spec_hash: 6e730fceb0f977b607e8d08d52f5e252 config_hash: eb32087403f958eead829e810f5a71b8 diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py index 6c55ba34..a31c8cad 100644 --- a/src/supermemory/resources/search.py +++ b/src/supermemory/resources/search.py @@ -56,6 +56,7 @@ def execute( include_summary: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, only_matching_chunks: bool | NotGiven = NOT_GIVEN, + rewrite_query: bool | NotGiven = NOT_GIVEN, user_id: str | NotGiven = NOT_GIVEN, # 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. @@ -87,6 +88,9 @@ def execute( only_matching_chunks: If true, only return matching chunks without context + rewrite_query: If true, rewrites the query to make it easier to find documents. This increases + the latency by about 400ms + user_id: End user ID this search is associated with extra_headers: Send extra headers @@ -110,6 +114,7 @@ def execute( "include_summary": include_summary, "limit": limit, "only_matching_chunks": only_matching_chunks, + "rewrite_query": rewrite_query, "user_id": user_id, }, search_execute_params.SearchExecuteParams, @@ -153,6 +158,7 @@ async def execute( include_summary: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, only_matching_chunks: bool | NotGiven = NOT_GIVEN, + rewrite_query: bool | NotGiven = NOT_GIVEN, user_id: str | NotGiven = NOT_GIVEN, # 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. @@ -184,6 +190,9 @@ async def execute( only_matching_chunks: If true, only return matching chunks without context + rewrite_query: If true, rewrites the query to make it easier to find documents. This increases + the latency by about 400ms + user_id: End user ID this search is associated with extra_headers: Send extra headers @@ -207,6 +216,7 @@ async def execute( "include_summary": include_summary, "limit": limit, "only_matching_chunks": only_matching_chunks, + "rewrite_query": rewrite_query, "user_id": user_id, }, search_execute_params.SearchExecuteParams, diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index 7e5a7ed2..936f7675 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -15,6 +15,9 @@ class Memory(BaseModel): id: str """Unique identifier of the memory""" + content: Optional[str] = None + """Content of the memory""" + created_at: datetime = FieldInfo(alias="createdAt") """Creation timestamp""" diff --git a/src/supermemory/types/search_execute_params.py b/src/supermemory/types/search_execute_params.py index 7ad50125..a6ec7301 100644 --- a/src/supermemory/types/search_execute_params.py +++ b/src/supermemory/types/search_execute_params.py @@ -43,6 +43,12 @@ class SearchExecuteParams(TypedDict, total=False): only_matching_chunks: Annotated[bool, PropertyInfo(alias="onlyMatchingChunks")] """If true, only return matching chunks without context""" + rewrite_query: Annotated[bool, PropertyInfo(alias="rewriteQuery")] + """If true, rewrites the query to make it easier to find documents. + + This increases the latency by about 400ms + """ + user_id: Annotated[str, PropertyInfo(alias="userId")] """End user ID this search is associated with""" diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py index 5bf10690..f3ed3549 100644 --- a/tests/api_resources/test_search.py +++ b/tests/api_resources/test_search.py @@ -54,6 +54,7 @@ def test_method_execute_with_all_params(self, client: Supermemory) -> None: include_summary=False, limit=10, only_matching_chunks=False, + rewrite_query=False, user_id="user_123", ) assert_matches_type(SearchExecuteResponse, search, path=["response"]) @@ -125,6 +126,7 @@ async def test_method_execute_with_all_params(self, async_client: AsyncSupermemo include_summary=False, limit=10, only_matching_chunks=False, + rewrite_query=False, user_id="user_123", ) assert_matches_type(SearchExecuteResponse, search, path=["response"]) From 7c0db70ec61ccd64197c333592457b925782f1ce Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 03:16:37 +0000 Subject: [PATCH 04/24] feat(api): api update --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index c3fa7868..810a2174 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-b581efe40ff877fc21a621d2e1b9419bf8126142f14f818416f69f6ffa376ddb.yml -openapi_spec_hash: 6e730fceb0f977b607e8d08d52f5e252 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-3a9edb70d1db5e4d84c9b36503c833028553baa1687ff9666ce508d044d9cd90.yml +openapi_spec_hash: 2e9e35933e87a849d9474045c8425731 config_hash: eb32087403f958eead829e810f5a71b8 From ed787b1abd49ebbc4e219f90bf71511306aafb2b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 04:16:31 +0000 Subject: [PATCH 05/24] feat(api): api update --- .stats.yml | 4 ++-- README.md | 10 +++++----- src/supermemory/resources/memory.py | 6 ++++-- tests/api_resources/test_memory.py | 16 ++++++++-------- tests/test_client.py | 24 ++++++++++-------------- 5 files changed, 29 insertions(+), 31 deletions(-) diff --git a/.stats.yml b/.stats.yml index 810a2174..d272718d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-3a9edb70d1db5e4d84c9b36503c833028553baa1687ff9666ce508d044d9cd90.yml -openapi_spec_hash: 2e9e35933e87a849d9474045c8425731 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-9d4f071b79b2524fee219bcfb0eafb1070969bb809e2f3dbee89392bfb3eac53.yml +openapi_spec_hash: 089ce871ebc7bda9d2974706373a989e config_hash: eb32087403f958eead829e810f5a71b8 diff --git a/README.md b/README.md index bc02d4c0..80699ce7 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ client = Supermemory( try: client.memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) except supermemory.APIConnectionError as e: print("The server could not be reached") @@ -135,7 +135,7 @@ client = Supermemory( # Or, configure per-request: client.with_options(max_retries=5).memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) ``` @@ -162,7 +162,7 @@ client = Supermemory( # Override per-request: client.with_options(timeout=5.0).memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) ``` @@ -207,7 +207,7 @@ client = Supermemory( api_key="My API Key", ) response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) print(response.headers.get('X-My-Header')) @@ -227,7 +227,7 @@ To stream the response body, use `.with_streaming_response` instead, which requi ```python with client.memory.with_streaming_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) as response: print(response.headers.get("X-My-Header")) diff --git a/src/supermemory/resources/memory.py b/src/supermemory/resources/memory.py index e0368257..349fdd2b 100644 --- a/src/supermemory/resources/memory.py +++ b/src/supermemory/resources/memory.py @@ -62,7 +62,8 @@ def create( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MemoryCreateResponse: """ - Add a new memory with content and metadata + Add or update a memory with any content type (text, url, file, etc.) with + metadata Args: content: Content of the memory @@ -256,7 +257,8 @@ async def create( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MemoryCreateResponse: """ - Add a new memory with content and metadata + Add or update a memory with any content type (text, url, file, etc.) with + metadata Args: content: Content of the memory diff --git a/tests/api_resources/test_memory.py b/tests/api_resources/test_memory.py index 871f95ce..c41dde7b 100644 --- a/tests/api_resources/test_memory.py +++ b/tests/api_resources/test_memory.py @@ -26,7 +26,7 @@ class TestMemory: @parametrize def test_method_create(self, client: Supermemory) -> None: memory = client.memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) assert_matches_type(MemoryCreateResponse, memory, path=["response"]) @@ -34,7 +34,7 @@ def test_method_create(self, client: Supermemory) -> None: @parametrize def test_method_create_with_all_params(self, client: Supermemory) -> None: memory = client.memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", id="id", metadata={ "source": "web", @@ -52,7 +52,7 @@ def test_method_create_with_all_params(self, client: Supermemory) -> None: @parametrize def test_raw_response_create(self, client: Supermemory) -> None: response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) assert response.is_closed is True @@ -64,7 +64,7 @@ def test_raw_response_create(self, client: Supermemory) -> None: @parametrize def test_streaming_response_create(self, client: Supermemory) -> None: with client.memory.with_streaming_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -206,7 +206,7 @@ class TestAsyncMemory: @parametrize async def test_method_create(self, async_client: AsyncSupermemory) -> None: memory = await async_client.memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) assert_matches_type(MemoryCreateResponse, memory, path=["response"]) @@ -214,7 +214,7 @@ async def test_method_create(self, async_client: AsyncSupermemory) -> None: @parametrize async def test_method_create_with_all_params(self, async_client: AsyncSupermemory) -> None: memory = await async_client.memory.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", id="id", metadata={ "source": "web", @@ -232,7 +232,7 @@ async def test_method_create_with_all_params(self, async_client: AsyncSupermemor @parametrize async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None: response = await async_client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) assert response.is_closed is True @@ -244,7 +244,7 @@ async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncSupermemory) -> None: async with async_client.memory.with_streaming_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/test_client.py b/tests/test_client.py index 30dc5897..d0f97a12 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -722,8 +722,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No body=cast( object, maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), - MemoryCreateParams, + dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams ), ), cast_to=httpx.Response, @@ -743,8 +742,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> Non body=cast( object, maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), - MemoryCreateParams, + dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams ), ), cast_to=httpx.Response, @@ -780,7 +778,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/add").mock(side_effect=retry_handler) response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..." + content="This is a detailed article about machine learning concepts.." ) assert response.retries_taken == failures_before_success @@ -806,7 +804,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/add").mock(side_effect=retry_handler) response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -832,7 +830,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/add").mock(side_effect=retry_handler) response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", extra_headers={"x-stainless-retry-count": "42"}, ) @@ -1508,8 +1506,7 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) body=cast( object, maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), - MemoryCreateParams, + dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams ), ), cast_to=httpx.Response, @@ -1529,8 +1526,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) body=cast( object, maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), - MemoryCreateParams, + dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams ), ), cast_to=httpx.Response, @@ -1567,7 +1563,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/add").mock(side_effect=retry_handler) response = await client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..." + content="This is a detailed article about machine learning concepts.." ) assert response.retries_taken == failures_before_success @@ -1594,7 +1590,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/add").mock(side_effect=retry_handler) response = await client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -1621,7 +1617,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/add").mock(side_effect=retry_handler) response = await client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts...", + content="This is a detailed article about machine learning concepts..", extra_headers={"x-stainless-retry-count": "42"}, ) From 4052baeca12183552a9bda674e97310e77b93623 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 05:16:37 +0000 Subject: [PATCH 06/24] feat(api): api update --- .stats.yml | 4 +- src/supermemory/resources/memory.py | 38 +++++++++++++--- src/supermemory/resources/search.py | 44 +++++++++++++------ src/supermemory/types/memory_create_params.py | 25 +++++++++-- src/supermemory/types/memory_get_response.py | 15 +++++-- .../types/search_execute_params.py | 30 ++++++++++--- 6 files changed, 122 insertions(+), 34 deletions(-) diff --git a/.stats.yml b/.stats.yml index d272718d..6cfb1c5a 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-9d4f071b79b2524fee219bcfb0eafb1070969bb809e2f3dbee89392bfb3eac53.yml -openapi_spec_hash: 089ce871ebc7bda9d2974706373a989e +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-783134192ebb9f62b9a25815c143704ccec4466fb4c5ba611d60d854db976970.yml +openapi_spec_hash: 2d0e081f9844f31ae00f95cff68ec29d config_hash: eb32087403f958eead829e810f5a71b8 diff --git a/src/supermemory/resources/memory.py b/src/supermemory/resources/memory.py index 349fdd2b..9bf4e180 100644 --- a/src/supermemory/resources/memory.py +++ b/src/supermemory/resources/memory.py @@ -66,11 +66,24 @@ def create( metadata Args: - content: Content of the memory + content: The content to extract and process into a memory. This can be a URL to a + website, a PDF, an image, or a video. - metadata: Optional metadata for the memory + Plaintext: Any plaintext format - user_id: Optional end user ID this memory belongs to + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + + metadata: Optional metadata for the memory. This is used to store additional information + about the memory. You can use this to store any additional information you need + about the memory. Metadata can be filtered through. Keys must be strings and are + case sensitive. Values can be strings, numbers, or booleans. You cannot nest + objects. + + user_id: Optional end user ID this memory belongs to. This is used to group memories by + user. You should use the same ID stored in your external system where the user + is stored extra_headers: Send extra headers @@ -261,11 +274,24 @@ async def create( metadata Args: - content: Content of the memory + content: The content to extract and process into a memory. This can be a URL to a + website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. - metadata: Optional metadata for the memory + metadata: Optional metadata for the memory. This is used to store additional information + about the memory. You can use this to store any additional information you need + about the memory. Metadata can be filtered through. Keys must be strings and are + case sensitive. Values can be strings, numbers, or booleans. You cannot nest + objects. - user_id: Optional end user ID this memory belongs to + user_id: Optional end user ID this memory belongs to. This is used to group memories by + user. You should use the same ID stored in your external system where the user + is stored extra_headers: Send extra headers diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py index a31c8cad..d34ef772 100644 --- a/src/supermemory/resources/search.py +++ b/src/supermemory/resources/search.py @@ -66,32 +66,40 @@ def execute( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SearchExecuteResponse: """ - Search through documents with metadata filtering + Search through documents with filtering Args: q: Search query string categories_filter: Optional category filters - chunk_threshold: Maximum number of chunks to return + chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most + chunks, more results), 1 is most sensitive (returns lesser chunks, accurate + results) - doc_id: Optional document ID to search within + doc_id: Optional document ID to search within. You can use this to find chunks in a very + large document. - document_threshold: Maximum number of documents to return + document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns + most documents, more results), 1 is most sensitive (returns lesser documents, + accurate results) filters: Optional filters to apply to the search include_summary: If true, include document summary in the response. This is helpful if you want a - chatbot to know the context of the document. + chatbot to know the full context of the document. limit: Maximum number of results to return - only_matching_chunks: If true, only return matching chunks without context + only_matching_chunks: If true, only return matching chunks without context. Normally, we send the + previous and next chunk to provide more context for LLMs. If you only want the + matching chunk, set this to true. rewrite_query: If true, rewrites the query to make it easier to find documents. This increases the latency by about 400ms - user_id: End user ID this search is associated with + user_id: End user ID this search is associated with. NOTE: This also acts as a filter for + the search. extra_headers: Send extra headers @@ -168,32 +176,40 @@ async def execute( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SearchExecuteResponse: """ - Search through documents with metadata filtering + Search through documents with filtering Args: q: Search query string categories_filter: Optional category filters - chunk_threshold: Maximum number of chunks to return + chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most + chunks, more results), 1 is most sensitive (returns lesser chunks, accurate + results) - doc_id: Optional document ID to search within + doc_id: Optional document ID to search within. You can use this to find chunks in a very + large document. - document_threshold: Maximum number of documents to return + document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns + most documents, more results), 1 is most sensitive (returns lesser documents, + accurate results) filters: Optional filters to apply to the search include_summary: If true, include document summary in the response. This is helpful if you want a - chatbot to know the context of the document. + chatbot to know the full context of the document. limit: Maximum number of results to return - only_matching_chunks: If true, only return matching chunks without context + only_matching_chunks: If true, only return matching chunks without context. Normally, we send the + previous and next chunk to provide more context for LLMs. If you only want the + matching chunk, set this to true. rewrite_query: If true, rewrites the query to make it easier to find documents. This increases the latency by about 400ms - user_id: End user ID this search is associated with + user_id: End user ID this search is associated with. NOTE: This also acts as a filter for + the search. extra_headers: Send extra headers diff --git a/src/supermemory/types/memory_create_params.py b/src/supermemory/types/memory_create_params.py index dc873287..938ba8e0 100644 --- a/src/supermemory/types/memory_create_params.py +++ b/src/supermemory/types/memory_create_params.py @@ -12,12 +12,31 @@ class MemoryCreateParams(TypedDict, total=False): content: Required[str] - """Content of the memory""" + """The content to extract and process into a memory. + + This can be a URL to a website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + """ id: str metadata: Dict[str, Union[str, float, bool]] - """Optional metadata for the memory""" + """Optional metadata for the memory. + + This is used to store additional information about the memory. You can use this + to store any additional information you need about the memory. Metadata can be + filtered through. Keys must be strings and are case sensitive. Values can be + strings, numbers, or booleans. You cannot nest objects. + """ user_id: Annotated[str, PropertyInfo(alias="userId")] - """Optional end user ID this memory belongs to""" + """Optional end user ID this memory belongs to. + + This is used to group memories by user. You should use the same ID stored in + your external system where the user is stored + """ diff --git a/src/supermemory/types/memory_get_response.py b/src/supermemory/types/memory_get_response.py index d915a18d..a2f008e6 100644 --- a/src/supermemory/types/memory_get_response.py +++ b/src/supermemory/types/memory_get_response.py @@ -1,6 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, Optional +from datetime import datetime +from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -10,18 +12,25 @@ class Doc(BaseModel): - created_at: str = FieldInfo(alias="createdAt") + created_at: datetime = FieldInfo(alias="createdAt") + """Timestamp when the memory was created""" - updated_at: str = FieldInfo(alias="updatedAt") + updated_at: datetime = FieldInfo(alias="updatedAt") + """Timestamp when the memory was last updated""" metadata: Optional[Dict[str, object]] = None + """Custom metadata associated with the memory""" summary: Optional[str] = None + """Summary of the memory content""" title: Optional[str] = None + """Title of the memory""" class MemoryGetResponse(BaseModel): doc: Doc + """Memory document details""" - status: Optional[str] = None + status: Optional[Literal["queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"]] = None + """Current processing status of the memory""" diff --git a/src/supermemory/types/search_execute_params.py b/src/supermemory/types/search_execute_params.py index a6ec7301..2895007a 100644 --- a/src/supermemory/types/search_execute_params.py +++ b/src/supermemory/types/search_execute_params.py @@ -20,13 +20,24 @@ class SearchExecuteParams(TypedDict, total=False): """Optional category filters""" chunk_threshold: Annotated[float, PropertyInfo(alias="chunkThreshold")] - """Maximum number of chunks to return""" + """Threshold / sensitivity for chunk selection. + + 0 is least sensitive (returns most chunks, more results), 1 is most sensitive + (returns lesser chunks, accurate results) + """ doc_id: Annotated[str, PropertyInfo(alias="docId")] - """Optional document ID to search within""" + """Optional document ID to search within. + + You can use this to find chunks in a very large document. + """ document_threshold: Annotated[float, PropertyInfo(alias="documentThreshold")] - """Maximum number of documents to return""" + """Threshold / sensitivity for document selection. + + 0 is least sensitive (returns most documents, more results), 1 is most sensitive + (returns lesser documents, accurate results) + """ filters: Filters """Optional filters to apply to the search""" @@ -34,14 +45,18 @@ class SearchExecuteParams(TypedDict, total=False): include_summary: Annotated[bool, PropertyInfo(alias="includeSummary")] """If true, include document summary in the response. - This is helpful if you want a chatbot to know the context of the document. + This is helpful if you want a chatbot to know the full context of the document. """ limit: int """Maximum number of results to return""" only_matching_chunks: Annotated[bool, PropertyInfo(alias="onlyMatchingChunks")] - """If true, only return matching chunks without context""" + """If true, only return matching chunks without context. + + Normally, we send the previous and next chunk to provide more context for LLMs. + If you only want the matching chunk, set this to true. + """ rewrite_query: Annotated[bool, PropertyInfo(alias="rewriteQuery")] """If true, rewrites the query to make it easier to find documents. @@ -50,7 +65,10 @@ class SearchExecuteParams(TypedDict, total=False): """ user_id: Annotated[str, PropertyInfo(alias="userId")] - """End user ID this search is associated with""" + """End user ID this search is associated with. + + NOTE: This also acts as a filter for the search. + """ class FiltersUnionMember0(TypedDict, total=False): From 722df6387d8fc3b38ee892d4382b19339a4b8165 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 08:16:38 +0000 Subject: [PATCH 07/24] feat(api): api update --- .stats.yml | 4 ++-- src/supermemory/resources/search.py | 10 ++++++++++ src/supermemory/types/memory_list_response.py | 5 ++++- src/supermemory/types/search_execute_params.py | 6 ++++++ tests/api_resources/test_search.py | 2 ++ 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.stats.yml b/.stats.yml index 6cfb1c5a..281212b4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-783134192ebb9f62b9a25815c143704ccec4466fb4c5ba611d60d854db976970.yml -openapi_spec_hash: 2d0e081f9844f31ae00f95cff68ec29d +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-571ecc4d367c9f140a08728c8e3b5c1cc4f5f8ae698e325c5cbe5181c2c87bb0.yml +openapi_spec_hash: b96a73f50568a58168354cb8690969e9 config_hash: eb32087403f958eead829e810f5a71b8 diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py index d34ef772..c6d8395a 100644 --- a/src/supermemory/resources/search.py +++ b/src/supermemory/resources/search.py @@ -56,6 +56,7 @@ def execute( include_summary: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, only_matching_chunks: bool | NotGiven = NOT_GIVEN, + rerank: bool | NotGiven = NOT_GIVEN, rewrite_query: bool | NotGiven = NOT_GIVEN, user_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -95,6 +96,9 @@ def execute( previous and next chunk to provide more context for LLMs. If you only want the matching chunk, set this to true. + rerank: If true, rerank the results based on the query. This is helpful if you want to + ensure the most relevant results are returned. + rewrite_query: If true, rewrites the query to make it easier to find documents. This increases the latency by about 400ms @@ -122,6 +126,7 @@ def execute( "include_summary": include_summary, "limit": limit, "only_matching_chunks": only_matching_chunks, + "rerank": rerank, "rewrite_query": rewrite_query, "user_id": user_id, }, @@ -166,6 +171,7 @@ async def execute( include_summary: bool | NotGiven = NOT_GIVEN, limit: int | NotGiven = NOT_GIVEN, only_matching_chunks: bool | NotGiven = NOT_GIVEN, + rerank: bool | NotGiven = NOT_GIVEN, rewrite_query: bool | NotGiven = NOT_GIVEN, user_id: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -205,6 +211,9 @@ async def execute( previous and next chunk to provide more context for LLMs. If you only want the matching chunk, set this to true. + rerank: If true, rerank the results based on the query. This is helpful if you want to + ensure the most relevant results are returned. + rewrite_query: If true, rewrites the query to make it easier to find documents. This increases the latency by about 400ms @@ -232,6 +241,7 @@ async def execute( "include_summary": include_summary, "limit": limit, "only_matching_chunks": only_matching_chunks, + "rerank": rerank, "rewrite_query": rewrite_query, "user_id": user_id, }, diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index 936f7675..06fff6b5 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -13,7 +13,10 @@ class Memory(BaseModel): id: str - """Unique identifier of the memory""" + """Unique identifier of the memory. + + This is your user's id in your system or database. + """ content: Optional[str] = None """Content of the memory""" diff --git a/src/supermemory/types/search_execute_params.py b/src/supermemory/types/search_execute_params.py index 2895007a..bc3b4756 100644 --- a/src/supermemory/types/search_execute_params.py +++ b/src/supermemory/types/search_execute_params.py @@ -58,6 +58,12 @@ class SearchExecuteParams(TypedDict, total=False): If you only want the matching chunk, set this to true. """ + rerank: bool + """If true, rerank the results based on the query. + + This is helpful if you want to ensure the most relevant results are returned. + """ + rewrite_query: Annotated[bool, PropertyInfo(alias="rewriteQuery")] """If true, rewrites the query to make it easier to find documents. diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py index f3ed3549..c0557ff5 100644 --- a/tests/api_resources/test_search.py +++ b/tests/api_resources/test_search.py @@ -54,6 +54,7 @@ def test_method_execute_with_all_params(self, client: Supermemory) -> None: include_summary=False, limit=10, only_matching_chunks=False, + rerank=False, rewrite_query=False, user_id="user_123", ) @@ -126,6 +127,7 @@ async def test_method_execute_with_all_params(self, async_client: AsyncSupermemo include_summary=False, limit=10, only_matching_chunks=False, + rerank=False, rewrite_query=False, user_id="user_123", ) From be6c667dbff65c00fc7f3bd22e541b477c19ca08 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 02:48:04 +0000 Subject: [PATCH 08/24] chore(internal): avoid errors for isinstance checks on proxies --- src/supermemory/_utils/_proxy.py | 5 ++++- tests/test_utils/test_proxy.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/supermemory/_utils/_proxy.py b/src/supermemory/_utils/_proxy.py index ffd883e9..0f239a33 100644 --- a/src/supermemory/_utils/_proxy.py +++ b/src/supermemory/_utils/_proxy.py @@ -46,7 +46,10 @@ def __dir__(self) -> Iterable[str]: @property # type: ignore @override def __class__(self) -> type: # pyright: ignore - proxied = self.__get_proxied__() + try: + proxied = self.__get_proxied__() + except Exception: + return type(self) if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ diff --git a/tests/test_utils/test_proxy.py b/tests/test_utils/test_proxy.py index cbb84efb..74440350 100644 --- a/tests/test_utils/test_proxy.py +++ b/tests/test_utils/test_proxy.py @@ -21,3 +21,14 @@ def test_recursive_proxy() -> None: assert dir(proxy) == [] assert type(proxy).__name__ == "RecursiveLazyProxy" assert type(operator.attrgetter("name.foo.bar.baz")(proxy)).__name__ == "RecursiveLazyProxy" + + +def test_isinstance_does_not_error() -> None: + class AlwaysErrorProxy(LazyProxy[Any]): + @override + def __load__(self) -> Any: + raise RuntimeError("Mocking missing dependency") + + proxy = AlwaysErrorProxy() + assert not isinstance(proxy, dict) + assert isinstance(proxy, LazyProxy) From aa2984202e3ff68031618847bc5a438e5a42933f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 10 May 2025 02:36:38 +0000 Subject: [PATCH 09/24] fix(package): support direct resource imports --- src/supermemory/__init__.py | 5 +++++ src/supermemory/_utils/_resources_proxy.py | 24 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/supermemory/_utils/_resources_proxy.py diff --git a/src/supermemory/__init__.py b/src/supermemory/__init__.py index a46b5100..3cc510e4 100644 --- a/src/supermemory/__init__.py +++ b/src/supermemory/__init__.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +import typing as _t + from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes from ._utils import file_from_path @@ -78,6 +80,9 @@ "DefaultAsyncHttpxClient", ] +if not _t.TYPE_CHECKING: + from ._utils._resources_proxy import resources as resources + _setup_logging() # Update the __module__ attribute for exported symbols so that diff --git a/src/supermemory/_utils/_resources_proxy.py b/src/supermemory/_utils/_resources_proxy.py new file mode 100644 index 00000000..53b457d1 --- /dev/null +++ b/src/supermemory/_utils/_resources_proxy.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Any +from typing_extensions import override + +from ._proxy import LazyProxy + + +class ResourcesProxy(LazyProxy[Any]): + """A proxy for the `supermemory.resources` module. + + This is used so that we can lazily import `supermemory.resources` only when + needed *and* so that users can just import `supermemory` and reference `supermemory.resources` + """ + + @override + def __load__(self) -> Any: + import importlib + + mod = importlib.import_module("supermemory.resources") + return mod + + +resources = ResourcesProxy().__as_proxied__() From 637811c4a31cfc9d258ca8562fee1cd38fb51320 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 14:42:31 +0000 Subject: [PATCH 10/24] feat(api): manual updates --- .stats.yml | 6 +- README.md | 63 ++-- api.md | 44 +-- src/supermemory/_client.py | 58 +-- src/supermemory/resources/__init__.py | 66 ++-- .../{connection.py => connections.py} | 196 +++++----- .../resources/{memory.py => memories.py} | 339 +++++++----------- src/supermemory/resources/search.py | 290 --------------- src/supermemory/resources/settings.py | 69 +++- src/supermemory/types/__init__.py | 11 +- .../types/connection_create_params.py | 15 - .../types/connection_get_response.py | 21 ++ .../types/connection_list_response.py | 25 ++ src/supermemory/types/memory_add_params.py | 18 + ...ate_response.py => memory_add_response.py} | 4 +- src/supermemory/types/memory_create_params.py | 42 --- .../types/memory_delete_response.py | 4 +- src/supermemory/types/memory_get_response.py | 31 +- src/supermemory/types/memory_list_params.py | 24 -- src/supermemory/types/memory_list_response.py | 55 +-- .../types/search_execute_params.py | 86 ----- .../types/search_execute_response.py | 52 --- src/supermemory/types/setting_get_response.py | 11 + .../types/setting_update_params.py | 16 +- .../types/setting_update_response.py | 14 +- tests/api_resources/test_connection.py | 200 ----------- tests/api_resources/test_connections.py | 230 ++++++++++++ .../{test_memory.py => test_memories.py} | 292 +++++++-------- tests/api_resources/test_search.py | 160 --------- tests/api_resources/test_settings.py | 74 +++- tests/test_client.py | 120 +++---- 31 files changed, 979 insertions(+), 1657 deletions(-) rename src/supermemory/resources/{connection.py => connections.py} (55%) rename src/supermemory/resources/{memory.py => memories.py} (62%) delete mode 100644 src/supermemory/resources/search.py delete mode 100644 src/supermemory/types/connection_create_params.py create mode 100644 src/supermemory/types/connection_get_response.py create mode 100644 src/supermemory/types/connection_list_response.py create mode 100644 src/supermemory/types/memory_add_params.py rename src/supermemory/types/{memory_create_response.py => memory_add_response.py} (67%) delete mode 100644 src/supermemory/types/memory_create_params.py delete mode 100644 src/supermemory/types/memory_list_params.py delete mode 100644 src/supermemory/types/search_execute_params.py delete mode 100644 src/supermemory/types/search_execute_response.py create mode 100644 src/supermemory/types/setting_get_response.py delete mode 100644 tests/api_resources/test_connection.py create mode 100644 tests/api_resources/test_connections.py rename tests/api_resources/{test_memory.py => test_memories.py} (70%) delete mode 100644 tests/api_resources/test_search.py diff --git a/.stats.yml b/.stats.yml index 281212b4..75349c0b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-571ecc4d367c9f140a08728c8e3b5c1cc4f5f8ae698e325c5cbe5181c2c87bb0.yml -openapi_spec_hash: b96a73f50568a58168354cb8690969e9 -config_hash: eb32087403f958eead829e810f5a71b8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-3fa2583744becce1e91ec5ad18f45d2cf17778def3a8f70537a15b08c746c2fb.yml +openapi_spec_hash: bf3c5827e7ddb8b32435aeb671fe7845 +config_hash: b9c958a39a94966479e516e9061818be diff --git a/README.md b/README.md index 80699ce7..9f05e6a6 100644 --- a/README.md +++ b/README.md @@ -24,36 +24,43 @@ pip install --pre supermemory The full API of this library can be found in [api.md](api.md). ```python +import os from supermemory import Supermemory client = Supermemory( - api_key="My API Key", + api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted ) -response = client.search.execute( - q="documents related to python", +memories = client.memories.list( + "id", ) -print(response.results) +print(memories.success) ``` +While you can provide an `api_key` keyword argument, +we recommend using [python-dotenv](https://pypi.org/project/python-dotenv/) +to add `SUPERMEMORY_API_KEY="My API Key"` to your `.env` file +so that your API Key is not stored in source control. + ## Async usage Simply import `AsyncSupermemory` instead of `Supermemory` and use `await` with each API call: ```python +import os import asyncio from supermemory import AsyncSupermemory client = AsyncSupermemory( - api_key="My API Key", + api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted ) async def main() -> None: - response = await client.search.execute( - q="documents related to python", + memories = await client.memories.list( + "id", ) - print(response.results) + print(memories.success) asyncio.run(main()) @@ -83,13 +90,11 @@ All errors inherit from `supermemory.APIError`. import supermemory from supermemory import Supermemory -client = Supermemory( - api_key="My API Key", -) +client = Supermemory() try: - client.memory.create( - content="This is a detailed article about machine learning concepts..", + client.memories.list( + "id", ) except supermemory.APIConnectionError as e: print("The server could not be reached") @@ -128,14 +133,13 @@ from supermemory import Supermemory # Configure the default for all requests: client = Supermemory( - api_key="My API Key", # default is 2 max_retries=0, ) # Or, configure per-request: -client.with_options(max_retries=5).memory.create( - content="This is a detailed article about machine learning concepts..", +client.with_options(max_retries=5).memories.list( + "id", ) ``` @@ -149,20 +153,18 @@ from supermemory import Supermemory # Configure the default for all requests: client = Supermemory( - api_key="My API Key", # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: client = Supermemory( - api_key="My API Key", timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) # Override per-request: -client.with_options(timeout=5.0).memory.create( - content="This is a detailed article about machine learning concepts..", +client.with_options(timeout=5.0).memories.list( + "id", ) ``` @@ -203,16 +205,14 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to ```py from supermemory import Supermemory -client = Supermemory( - api_key="My API Key", -) -response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", +client = Supermemory() +response = client.memories.with_raw_response.list( + "id", ) print(response.headers.get('X-My-Header')) -memory = response.parse() # get the object that `memory.create()` would have returned -print(memory.id) +memory = response.parse() # get the object that `memories.list()` would have returned +print(memory.success) ``` These methods return an [`APIResponse`](https://github.com/supermemoryai/python-sdk/tree/main/src/supermemory/_response.py) object. @@ -226,8 +226,8 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.memory.with_streaming_response.create( - content="This is a detailed article about machine learning concepts..", +with client.memories.with_streaming_response.list( + "id", ) as response: print(response.headers.get("X-My-Header")) @@ -284,7 +284,6 @@ import httpx from supermemory import Supermemory, DefaultHttpxClient client = Supermemory( - api_key="My API Key", # Or use the `SUPERMEMORY_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( @@ -307,9 +306,7 @@ By default the library closes underlying HTTP connections whenever the client is ```py from supermemory import Supermemory -with Supermemory( - api_key="My API Key", -) as client: +with Supermemory() as client: # make requests here ... diff --git a/api.md b/api.md index 337614f6..ea51d4ff 100644 --- a/api.md +++ b/api.md @@ -1,56 +1,50 @@ -# Settings - -Types: - -```python -from supermemory.types import SettingUpdateResponse -``` - -Methods: - -- client.settings.update(\*\*params) -> SettingUpdateResponse - -# Memory +# Memories Types: ```python from supermemory.types import ( - MemoryCreateResponse, MemoryListResponse, MemoryDeleteResponse, + MemoryAddResponse, MemoryGetResponse, ) ``` Methods: -- client.memory.create(\*\*params) -> MemoryCreateResponse -- client.memory.list(\*\*params) -> MemoryListResponse -- client.memory.delete(id) -> MemoryDeleteResponse -- client.memory.get(id) -> MemoryGetResponse +- client.memories.list(id) -> MemoryListResponse +- client.memories.delete(id) -> MemoryDeleteResponse +- client.memories.add(\*\*params) -> MemoryAddResponse +- client.memories.get(id) -> MemoryGetResponse -# Search +# Settings Types: ```python -from supermemory.types import SearchExecuteResponse +from supermemory.types import SettingUpdateResponse, SettingGetResponse ``` Methods: -- client.search.execute(\*\*params) -> SearchExecuteResponse +- client.settings.update(\*\*params) -> SettingUpdateResponse +- client.settings.get() -> SettingGetResponse -# Connection +# Connections Types: ```python -from supermemory.types import ConnectionCreateResponse +from supermemory.types import ( + ConnectionCreateResponse, + ConnectionListResponse, + ConnectionGetResponse, +) ``` Methods: -- client.connection.create(app, \*\*params) -> ConnectionCreateResponse -- client.connection.retrieve(connection_id) -> None +- client.connections.create(provider) -> ConnectionCreateResponse +- client.connections.list() -> ConnectionListResponse +- client.connections.get(connection_id) -> ConnectionGetResponse diff --git a/src/supermemory/_client.py b/src/supermemory/_client.py index 266329fc..0a336bf6 100644 --- a/src/supermemory/_client.py +++ b/src/supermemory/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import memory, search, settings, connection +from .resources import memories, settings, connections from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, SupermemoryError from ._base_client import ( @@ -43,10 +43,9 @@ class Supermemory(SyncAPIClient): + memories: memories.MemoriesResource settings: settings.SettingsResource - memory: memory.MemoryResource - search: search.SearchResource - connection: connection.ConnectionResource + connections: connections.ConnectionsResource with_raw_response: SupermemoryWithRawResponse with_streaming_response: SupermemoryWithStreamedResponse @@ -91,7 +90,7 @@ def __init__( if base_url is None: base_url = os.environ.get("SUPERMEMORY_BASE_URL") if base_url is None: - base_url = f"https://v2.api.supermemory.ai" + base_url = f"https://api.supermemory.ai/" super().__init__( version=__version__, @@ -104,10 +103,9 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self.memories = memories.MemoriesResource(self) self.settings = settings.SettingsResource(self) - self.memory = memory.MemoryResource(self) - self.search = search.SearchResource(self) - self.connection = connection.ConnectionResource(self) + self.connections = connections.ConnectionsResource(self) self.with_raw_response = SupermemoryWithRawResponse(self) self.with_streaming_response = SupermemoryWithStreamedResponse(self) @@ -116,6 +114,12 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + api_key = self.api_key + return {"Authorization": f"Bearer {api_key}"} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -211,10 +215,9 @@ def _make_status_error( class AsyncSupermemory(AsyncAPIClient): + memories: memories.AsyncMemoriesResource settings: settings.AsyncSettingsResource - memory: memory.AsyncMemoryResource - search: search.AsyncSearchResource - connection: connection.AsyncConnectionResource + connections: connections.AsyncConnectionsResource with_raw_response: AsyncSupermemoryWithRawResponse with_streaming_response: AsyncSupermemoryWithStreamedResponse @@ -259,7 +262,7 @@ def __init__( if base_url is None: base_url = os.environ.get("SUPERMEMORY_BASE_URL") if base_url is None: - base_url = f"https://v2.api.supermemory.ai" + base_url = f"https://api.supermemory.ai/" super().__init__( version=__version__, @@ -272,10 +275,9 @@ def __init__( _strict_response_validation=_strict_response_validation, ) + self.memories = memories.AsyncMemoriesResource(self) self.settings = settings.AsyncSettingsResource(self) - self.memory = memory.AsyncMemoryResource(self) - self.search = search.AsyncSearchResource(self) - self.connection = connection.AsyncConnectionResource(self) + self.connections = connections.AsyncConnectionsResource(self) self.with_raw_response = AsyncSupermemoryWithRawResponse(self) self.with_streaming_response = AsyncSupermemoryWithStreamedResponse(self) @@ -284,6 +286,12 @@ def __init__( def qs(self) -> Querystring: return Querystring(array_format="comma") + @property + @override + def auth_headers(self) -> dict[str, str]: + api_key = self.api_key + return {"Authorization": f"Bearer {api_key}"} + @property @override def default_headers(self) -> dict[str, str | Omit]: @@ -380,34 +388,30 @@ def _make_status_error( class SupermemoryWithRawResponse: def __init__(self, client: Supermemory) -> None: + self.memories = memories.MemoriesResourceWithRawResponse(client.memories) self.settings = settings.SettingsResourceWithRawResponse(client.settings) - self.memory = memory.MemoryResourceWithRawResponse(client.memory) - self.search = search.SearchResourceWithRawResponse(client.search) - self.connection = connection.ConnectionResourceWithRawResponse(client.connection) + self.connections = connections.ConnectionsResourceWithRawResponse(client.connections) class AsyncSupermemoryWithRawResponse: def __init__(self, client: AsyncSupermemory) -> None: + self.memories = memories.AsyncMemoriesResourceWithRawResponse(client.memories) self.settings = settings.AsyncSettingsResourceWithRawResponse(client.settings) - self.memory = memory.AsyncMemoryResourceWithRawResponse(client.memory) - self.search = search.AsyncSearchResourceWithRawResponse(client.search) - self.connection = connection.AsyncConnectionResourceWithRawResponse(client.connection) + self.connections = connections.AsyncConnectionsResourceWithRawResponse(client.connections) class SupermemoryWithStreamedResponse: def __init__(self, client: Supermemory) -> None: + self.memories = memories.MemoriesResourceWithStreamingResponse(client.memories) self.settings = settings.SettingsResourceWithStreamingResponse(client.settings) - self.memory = memory.MemoryResourceWithStreamingResponse(client.memory) - self.search = search.SearchResourceWithStreamingResponse(client.search) - self.connection = connection.ConnectionResourceWithStreamingResponse(client.connection) + self.connections = connections.ConnectionsResourceWithStreamingResponse(client.connections) class AsyncSupermemoryWithStreamedResponse: def __init__(self, client: AsyncSupermemory) -> None: + self.memories = memories.AsyncMemoriesResourceWithStreamingResponse(client.memories) self.settings = settings.AsyncSettingsResourceWithStreamingResponse(client.settings) - self.memory = memory.AsyncMemoryResourceWithStreamingResponse(client.memory) - self.search = search.AsyncSearchResourceWithStreamingResponse(client.search) - self.connection = connection.AsyncConnectionResourceWithStreamingResponse(client.connection) + self.connections = connections.AsyncConnectionsResourceWithStreamingResponse(client.connections) Client = Supermemory diff --git a/src/supermemory/resources/__init__.py b/src/supermemory/resources/__init__.py index b772e05e..5a1fb723 100644 --- a/src/supermemory/resources/__init__.py +++ b/src/supermemory/resources/__init__.py @@ -1,20 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from .memory import ( - MemoryResource, - AsyncMemoryResource, - MemoryResourceWithRawResponse, - AsyncMemoryResourceWithRawResponse, - MemoryResourceWithStreamingResponse, - AsyncMemoryResourceWithStreamingResponse, -) -from .search import ( - SearchResource, - AsyncSearchResource, - SearchResourceWithRawResponse, - AsyncSearchResourceWithRawResponse, - SearchResourceWithStreamingResponse, - AsyncSearchResourceWithStreamingResponse, +from .memories import ( + MemoriesResource, + AsyncMemoriesResource, + MemoriesResourceWithRawResponse, + AsyncMemoriesResourceWithRawResponse, + MemoriesResourceWithStreamingResponse, + AsyncMemoriesResourceWithStreamingResponse, ) from .settings import ( SettingsResource, @@ -24,38 +16,32 @@ SettingsResourceWithStreamingResponse, AsyncSettingsResourceWithStreamingResponse, ) -from .connection import ( - ConnectionResource, - AsyncConnectionResource, - ConnectionResourceWithRawResponse, - AsyncConnectionResourceWithRawResponse, - ConnectionResourceWithStreamingResponse, - AsyncConnectionResourceWithStreamingResponse, +from .connections import ( + ConnectionsResource, + AsyncConnectionsResource, + ConnectionsResourceWithRawResponse, + AsyncConnectionsResourceWithRawResponse, + ConnectionsResourceWithStreamingResponse, + AsyncConnectionsResourceWithStreamingResponse, ) __all__ = [ + "MemoriesResource", + "AsyncMemoriesResource", + "MemoriesResourceWithRawResponse", + "AsyncMemoriesResourceWithRawResponse", + "MemoriesResourceWithStreamingResponse", + "AsyncMemoriesResourceWithStreamingResponse", "SettingsResource", "AsyncSettingsResource", "SettingsResourceWithRawResponse", "AsyncSettingsResourceWithRawResponse", "SettingsResourceWithStreamingResponse", "AsyncSettingsResourceWithStreamingResponse", - "MemoryResource", - "AsyncMemoryResource", - "MemoryResourceWithRawResponse", - "AsyncMemoryResourceWithRawResponse", - "MemoryResourceWithStreamingResponse", - "AsyncMemoryResourceWithStreamingResponse", - "SearchResource", - "AsyncSearchResource", - "SearchResourceWithRawResponse", - "AsyncSearchResourceWithRawResponse", - "SearchResourceWithStreamingResponse", - "AsyncSearchResourceWithStreamingResponse", - "ConnectionResource", - "AsyncConnectionResource", - "ConnectionResourceWithRawResponse", - "AsyncConnectionResourceWithRawResponse", - "ConnectionResourceWithStreamingResponse", - "AsyncConnectionResourceWithStreamingResponse", + "ConnectionsResource", + "AsyncConnectionsResource", + "ConnectionsResourceWithRawResponse", + "AsyncConnectionsResourceWithRawResponse", + "ConnectionsResourceWithStreamingResponse", + "AsyncConnectionsResourceWithStreamingResponse", ] diff --git a/src/supermemory/resources/connection.py b/src/supermemory/resources/connections.py similarity index 55% rename from src/supermemory/resources/connection.py rename to src/supermemory/resources/connections.py index a55ea8b7..75181a0d 100644 --- a/src/supermemory/resources/connection.py +++ b/src/supermemory/resources/connections.py @@ -6,9 +6,7 @@ import httpx -from ..types import connection_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven -from .._utils import maybe_transform, async_maybe_transform +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -18,37 +16,37 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.connection_get_response import ConnectionGetResponse +from ..types.connection_list_response import ConnectionListResponse from ..types.connection_create_response import ConnectionCreateResponse -__all__ = ["ConnectionResource", "AsyncConnectionResource"] +__all__ = ["ConnectionsResource", "AsyncConnectionsResource"] -class ConnectionResource(SyncAPIResource): +class ConnectionsResource(SyncAPIResource): @cached_property - def with_raw_response(self) -> ConnectionResourceWithRawResponse: + def with_raw_response(self) -> ConnectionsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers """ - return ConnectionResourceWithRawResponse(self) + return ConnectionsResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> ConnectionResourceWithStreamingResponse: + def with_streaming_response(self) -> ConnectionsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response """ - return ConnectionResourceWithStreamingResponse(self) + return ConnectionsResourceWithStreamingResponse(self) def create( self, - app: Literal["notion", "google-drive"], + provider: Literal["notion", "google-drive", "onedrive"], *, - id: str, - redirect_url: str | NotGiven = NOT_GIVEN, # 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, @@ -68,27 +66,36 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - if not app: - raise ValueError(f"Expected a non-empty value for `app` but received {app!r}") - return self._get( - f"/connect/{app}", + if not provider: + raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") + return self._post( + f"/v3/connections/{provider}", options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "id": id, - "redirect_url": redirect_url, - }, - connection_create_params.ConnectionCreateParams, - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=ConnectionCreateResponse, ) - def retrieve( + def list( + self, + *, + # 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, + ) -> ConnectionListResponse: + """List all connections""" + return self._get( + "/v3/connections", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConnectionListResponse, + ) + + def get( self, connection_id: str, *, @@ -98,8 +105,10 @@ def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> None: + ) -> ConnectionGetResponse: """ + Get connection details + Args: extra_headers: Send extra headers @@ -111,42 +120,39 @@ def retrieve( """ if not connection_id: raise ValueError(f"Expected a non-empty value for `connection_id` but received {connection_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._get( - f"/connections/{connection_id}", + f"/v3/connections/{connection_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=ConnectionGetResponse, ) -class AsyncConnectionResource(AsyncAPIResource): +class AsyncConnectionsResource(AsyncAPIResource): @cached_property - def with_raw_response(self) -> AsyncConnectionResourceWithRawResponse: + def with_raw_response(self) -> AsyncConnectionsResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers """ - return AsyncConnectionResourceWithRawResponse(self) + return AsyncConnectionsResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> AsyncConnectionResourceWithStreamingResponse: + def with_streaming_response(self) -> AsyncConnectionsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response """ - return AsyncConnectionResourceWithStreamingResponse(self) + return AsyncConnectionsResourceWithStreamingResponse(self) async def create( self, - app: Literal["notion", "google-drive"], + provider: Literal["notion", "google-drive", "onedrive"], *, - id: str, - redirect_url: str | NotGiven = NOT_GIVEN, # 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, @@ -166,27 +172,36 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - if not app: - raise ValueError(f"Expected a non-empty value for `app` but received {app!r}") - return await self._get( - f"/connect/{app}", + if not provider: + raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") + return await self._post( + f"/v3/connections/{provider}", options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "id": id, - "redirect_url": redirect_url, - }, - connection_create_params.ConnectionCreateParams, - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=ConnectionCreateResponse, ) - async def retrieve( + async def list( + self, + *, + # 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, + ) -> ConnectionListResponse: + """List all connections""" + return await self._get( + "/v3/connections", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=ConnectionListResponse, + ) + + async def get( self, connection_id: str, *, @@ -196,8 +211,10 @@ async def retrieve( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> None: + ) -> ConnectionGetResponse: """ + Get connection details + Args: extra_headers: Send extra headers @@ -209,59 +226,70 @@ async def retrieve( """ if not connection_id: raise ValueError(f"Expected a non-empty value for `connection_id` but received {connection_id!r}") - extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._get( - f"/connections/{connection_id}", + f"/v3/connections/{connection_id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=ConnectionGetResponse, ) -class ConnectionResourceWithRawResponse: - def __init__(self, connection: ConnectionResource) -> None: - self._connection = connection +class ConnectionsResourceWithRawResponse: + def __init__(self, connections: ConnectionsResource) -> None: + self._connections = connections self.create = to_raw_response_wrapper( - connection.create, + connections.create, ) - self.retrieve = to_raw_response_wrapper( - connection.retrieve, + self.list = to_raw_response_wrapper( + connections.list, + ) + self.get = to_raw_response_wrapper( + connections.get, ) -class AsyncConnectionResourceWithRawResponse: - def __init__(self, connection: AsyncConnectionResource) -> None: - self._connection = connection +class AsyncConnectionsResourceWithRawResponse: + def __init__(self, connections: AsyncConnectionsResource) -> None: + self._connections = connections self.create = async_to_raw_response_wrapper( - connection.create, + connections.create, + ) + self.list = async_to_raw_response_wrapper( + connections.list, ) - self.retrieve = async_to_raw_response_wrapper( - connection.retrieve, + self.get = async_to_raw_response_wrapper( + connections.get, ) -class ConnectionResourceWithStreamingResponse: - def __init__(self, connection: ConnectionResource) -> None: - self._connection = connection +class ConnectionsResourceWithStreamingResponse: + def __init__(self, connections: ConnectionsResource) -> None: + self._connections = connections self.create = to_streamed_response_wrapper( - connection.create, + connections.create, ) - self.retrieve = to_streamed_response_wrapper( - connection.retrieve, + self.list = to_streamed_response_wrapper( + connections.list, + ) + self.get = to_streamed_response_wrapper( + connections.get, ) -class AsyncConnectionResourceWithStreamingResponse: - def __init__(self, connection: AsyncConnectionResource) -> None: - self._connection = connection +class AsyncConnectionsResourceWithStreamingResponse: + def __init__(self, connections: AsyncConnectionsResource) -> None: + self._connections = connections self.create = async_to_streamed_response_wrapper( - connection.create, + connections.create, + ) + self.list = async_to_streamed_response_wrapper( + connections.list, ) - self.retrieve = async_to_streamed_response_wrapper( - connection.retrieve, + self.get = async_to_streamed_response_wrapper( + connections.get, ) diff --git a/src/supermemory/resources/memory.py b/src/supermemory/resources/memories.py similarity index 62% rename from src/supermemory/resources/memory.py rename to src/supermemory/resources/memories.py index 9bf4e180..891d1b6a 100644 --- a/src/supermemory/resources/memory.py +++ b/src/supermemory/resources/memories.py @@ -2,12 +2,11 @@ from __future__ import annotations -from typing import Dict, Union -from typing_extensions import Literal +from typing import Dict, List, Union import httpx -from ..types import memory_list_params, memory_create_params +from ..types import memory_add_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -19,72 +18,49 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.memory_add_response import MemoryAddResponse from ..types.memory_get_response import MemoryGetResponse from ..types.memory_list_response import MemoryListResponse -from ..types.memory_create_response import MemoryCreateResponse from ..types.memory_delete_response import MemoryDeleteResponse -__all__ = ["MemoryResource", "AsyncMemoryResource"] +__all__ = ["MemoriesResource", "AsyncMemoriesResource"] -class MemoryResource(SyncAPIResource): +class MemoriesResource(SyncAPIResource): @cached_property - def with_raw_response(self) -> MemoryResourceWithRawResponse: + def with_raw_response(self) -> MemoriesResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers """ - return MemoryResourceWithRawResponse(self) + return MemoriesResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> MemoryResourceWithStreamingResponse: + def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response """ - return MemoryResourceWithStreamingResponse(self) + return MemoriesResourceWithStreamingResponse(self) - def create( + def list( self, + id: str, *, - content: str, - id: str | NotGiven = NOT_GIVEN, - metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, - user_id: str | NotGiven = NOT_GIVEN, # 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, - ) -> MemoryCreateResponse: + ) -> MemoryListResponse: """ - Add or update a memory with any content type (text, url, file, etc.) with - metadata + Delete a memory Args: - content: The content to extract and process into a memory. This can be a URL to a - website, a PDF, an image, or a video. - - Plaintext: Any plaintext format - - URL: A URL to a website, PDF, image, or video - - We automatically detect the content type from the url's response format. - - metadata: Optional metadata for the memory. This is used to store additional information - about the memory. You can use this to store any additional information you need - about the memory. Metadata can be filtered through. Keys must be strings and are - case sensitive. Values can be strings, numbers, or booleans. You cannot nest - objects. - - user_id: Optional end user ID this memory belongs to. This is used to group memories by - user. You should use the same ID stored in your external system where the user - is stored - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -93,52 +69,31 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/add", - body=maybe_transform( - { - "content": content, - "id": id, - "metadata": metadata, - "user_id": user_id, - }, - memory_create_params.MemoryCreateParams, - ), + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._delete( + f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryCreateResponse, + cast_to=MemoryListResponse, ) - def list( + def delete( self, + id: str, *, - filters: str | NotGiven = NOT_GIVEN, - limit: str | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - page: str | NotGiven = NOT_GIVEN, - sort: Literal["createdAt", "updatedAt"] | NotGiven = NOT_GIVEN, # 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, - ) -> MemoryListResponse: + ) -> MemoryDeleteResponse: """ - Retrieves a paginated list of memories with their metadata and workflow status + Get a memory by ID Args: - filters: Optional filters to apply to the search - - limit: Number of items per page - - order: Sort order - - page: Page number to fetch - - sort: Field to sort by - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -147,40 +102,31 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - "/memories", + f"/v3/memories/{id}", options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "filters": filters, - "limit": limit, - "order": order, - "page": page, - "sort": sort, - }, - memory_list_params.MemoryListParams, - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryListResponse, + cast_to=MemoryDeleteResponse, ) - def delete( + def add( self, - id: str, *, + content: str, + container_tags: List[str] | NotGiven = NOT_GIVEN, + metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, # 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: + ) -> MemoryAddResponse: """ - Delete a memory + Add a memory with any content type (text, url, file, etc.) and metadata Args: extra_headers: Send extra headers @@ -191,14 +137,20 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._delete( - f"/delete/{id}", + return self._post( + "/v3/memories", + body=maybe_transform( + { + "content": content, + "container_tags": container_tags, + "metadata": metadata, + }, + memory_add_params.MemoryAddParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryDeleteResponse, + cast_to=MemoryAddResponse, ) def get( @@ -227,7 +179,7 @@ def get( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._get( - f"/memory/{id}", + f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -235,64 +187,41 @@ def get( ) -class AsyncMemoryResource(AsyncAPIResource): +class AsyncMemoriesResource(AsyncAPIResource): @cached_property - def with_raw_response(self) -> AsyncMemoryResourceWithRawResponse: + def with_raw_response(self) -> AsyncMemoriesResourceWithRawResponse: """ This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers """ - return AsyncMemoryResourceWithRawResponse(self) + return AsyncMemoriesResourceWithRawResponse(self) @cached_property - def with_streaming_response(self) -> AsyncMemoryResourceWithStreamingResponse: + def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response """ - return AsyncMemoryResourceWithStreamingResponse(self) + return AsyncMemoriesResourceWithStreamingResponse(self) - async def create( + async def list( self, + id: str, *, - content: str, - id: str | NotGiven = NOT_GIVEN, - metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, - user_id: str | NotGiven = NOT_GIVEN, # 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, - ) -> MemoryCreateResponse: + ) -> MemoryListResponse: """ - Add or update a memory with any content type (text, url, file, etc.) with - metadata + Delete a memory Args: - content: The content to extract and process into a memory. This can be a URL to a - website, a PDF, an image, or a video. - - Plaintext: Any plaintext format - - URL: A URL to a website, PDF, image, or video - - We automatically detect the content type from the url's response format. - - metadata: Optional metadata for the memory. This is used to store additional information - about the memory. You can use this to store any additional information you need - about the memory. Metadata can be filtered through. Keys must be strings and are - case sensitive. Values can be strings, numbers, or booleans. You cannot nest - objects. - - user_id: Optional end user ID this memory belongs to. This is used to group memories by - user. You should use the same ID stored in your external system where the user - is stored - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -301,52 +230,31 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/add", - body=await async_maybe_transform( - { - "content": content, - "id": id, - "metadata": metadata, - "user_id": user_id, - }, - memory_create_params.MemoryCreateParams, - ), + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._delete( + f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryCreateResponse, + cast_to=MemoryListResponse, ) - async def list( + async def delete( self, + id: str, *, - filters: str | NotGiven = NOT_GIVEN, - limit: str | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - page: str | NotGiven = NOT_GIVEN, - sort: Literal["createdAt", "updatedAt"] | NotGiven = NOT_GIVEN, # 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, - ) -> MemoryListResponse: + ) -> MemoryDeleteResponse: """ - Retrieves a paginated list of memories with their metadata and workflow status + Get a memory by ID Args: - filters: Optional filters to apply to the search - - limit: Number of items per page - - order: Sort order - - page: Page number to fetch - - sort: Field to sort by - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -355,40 +263,31 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - "/memories", + f"/v3/memories/{id}", options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "filters": filters, - "limit": limit, - "order": order, - "page": page, - "sort": sort, - }, - memory_list_params.MemoryListParams, - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryListResponse, + cast_to=MemoryDeleteResponse, ) - async def delete( + async def add( self, - id: str, *, + content: str, + container_tags: List[str] | NotGiven = NOT_GIVEN, + metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, # 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: + ) -> MemoryAddResponse: """ - Delete a memory + Add a memory with any content type (text, url, file, etc.) and metadata Args: extra_headers: Send extra headers @@ -399,14 +298,20 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._delete( - f"/delete/{id}", + return await self._post( + "/v3/memories", + body=await async_maybe_transform( + { + "content": content, + "container_tags": container_tags, + "metadata": metadata, + }, + memory_add_params.MemoryAddParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryDeleteResponse, + cast_to=MemoryAddResponse, ) async def get( @@ -435,7 +340,7 @@ async def get( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._get( - f"/memory/{id}", + f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -443,73 +348,73 @@ async def get( ) -class MemoryResourceWithRawResponse: - def __init__(self, memory: MemoryResource) -> None: - self._memory = memory +class MemoriesResourceWithRawResponse: + def __init__(self, memories: MemoriesResource) -> None: + self._memories = memories - self.create = to_raw_response_wrapper( - memory.create, - ) self.list = to_raw_response_wrapper( - memory.list, + memories.list, ) self.delete = to_raw_response_wrapper( - memory.delete, + memories.delete, + ) + self.add = to_raw_response_wrapper( + memories.add, ) self.get = to_raw_response_wrapper( - memory.get, + memories.get, ) -class AsyncMemoryResourceWithRawResponse: - def __init__(self, memory: AsyncMemoryResource) -> None: - self._memory = memory +class AsyncMemoriesResourceWithRawResponse: + def __init__(self, memories: AsyncMemoriesResource) -> None: + self._memories = memories - self.create = async_to_raw_response_wrapper( - memory.create, - ) self.list = async_to_raw_response_wrapper( - memory.list, + memories.list, ) self.delete = async_to_raw_response_wrapper( - memory.delete, + memories.delete, + ) + self.add = async_to_raw_response_wrapper( + memories.add, ) self.get = async_to_raw_response_wrapper( - memory.get, + memories.get, ) -class MemoryResourceWithStreamingResponse: - def __init__(self, memory: MemoryResource) -> None: - self._memory = memory +class MemoriesResourceWithStreamingResponse: + def __init__(self, memories: MemoriesResource) -> None: + self._memories = memories - self.create = to_streamed_response_wrapper( - memory.create, - ) self.list = to_streamed_response_wrapper( - memory.list, + memories.list, ) self.delete = to_streamed_response_wrapper( - memory.delete, + memories.delete, + ) + self.add = to_streamed_response_wrapper( + memories.add, ) self.get = to_streamed_response_wrapper( - memory.get, + memories.get, ) -class AsyncMemoryResourceWithStreamingResponse: - def __init__(self, memory: AsyncMemoryResource) -> None: - self._memory = memory +class AsyncMemoriesResourceWithStreamingResponse: + def __init__(self, memories: AsyncMemoriesResource) -> None: + self._memories = memories - self.create = async_to_streamed_response_wrapper( - memory.create, - ) self.list = async_to_streamed_response_wrapper( - memory.list, + memories.list, ) self.delete = async_to_streamed_response_wrapper( - memory.delete, + memories.delete, + ) + self.add = async_to_streamed_response_wrapper( + memories.add, ) self.get = async_to_streamed_response_wrapper( - memory.get, + memories.get, ) diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py deleted file mode 100644 index c6d8395a..00000000 --- a/src/supermemory/resources/search.py +++ /dev/null @@ -1,290 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List -from typing_extensions import Literal - -import httpx - -from ..types import search_execute_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.search_execute_response import SearchExecuteResponse - -__all__ = ["SearchResource", "AsyncSearchResource"] - - -class SearchResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> SearchResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers - """ - return SearchResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> SearchResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response - """ - return SearchResourceWithStreamingResponse(self) - - def execute( - self, - *, - q: str, - categories_filter: List[Literal["technology", "science", "business", "health"]] | NotGiven = NOT_GIVEN, - chunk_threshold: float | NotGiven = NOT_GIVEN, - doc_id: str | NotGiven = NOT_GIVEN, - document_threshold: float | NotGiven = NOT_GIVEN, - filters: search_execute_params.Filters | NotGiven = NOT_GIVEN, - include_summary: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - only_matching_chunks: bool | NotGiven = NOT_GIVEN, - rerank: bool | NotGiven = NOT_GIVEN, - rewrite_query: bool | NotGiven = NOT_GIVEN, - user_id: str | NotGiven = NOT_GIVEN, - # 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, - ) -> SearchExecuteResponse: - """ - Search through documents with filtering - - Args: - q: Search query string - - categories_filter: Optional category filters - - chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most - chunks, more results), 1 is most sensitive (returns lesser chunks, accurate - results) - - doc_id: Optional document ID to search within. You can use this to find chunks in a very - large document. - - document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns - most documents, more results), 1 is most sensitive (returns lesser documents, - accurate results) - - filters: Optional filters to apply to the search - - include_summary: If true, include document summary in the response. This is helpful if you want a - chatbot to know the full context of the document. - - limit: Maximum number of results to return - - only_matching_chunks: If true, only return matching chunks without context. Normally, we send the - previous and next chunk to provide more context for LLMs. If you only want the - matching chunk, set this to true. - - rerank: If true, rerank the results based on the query. This is helpful if you want to - ensure the most relevant results are returned. - - rewrite_query: If true, rewrites the query to make it easier to find documents. This increases - the latency by about 400ms - - user_id: End user ID this search is associated with. NOTE: This also acts as a filter for - the search. - - 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 - """ - return self._post( - "/search", - body=maybe_transform( - { - "q": q, - "categories_filter": categories_filter, - "chunk_threshold": chunk_threshold, - "doc_id": doc_id, - "document_threshold": document_threshold, - "filters": filters, - "include_summary": include_summary, - "limit": limit, - "only_matching_chunks": only_matching_chunks, - "rerank": rerank, - "rewrite_query": rewrite_query, - "user_id": user_id, - }, - search_execute_params.SearchExecuteParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SearchExecuteResponse, - ) - - -class AsyncSearchResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncSearchResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncSearchResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncSearchResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response - """ - return AsyncSearchResourceWithStreamingResponse(self) - - async def execute( - self, - *, - q: str, - categories_filter: List[Literal["technology", "science", "business", "health"]] | NotGiven = NOT_GIVEN, - chunk_threshold: float | NotGiven = NOT_GIVEN, - doc_id: str | NotGiven = NOT_GIVEN, - document_threshold: float | NotGiven = NOT_GIVEN, - filters: search_execute_params.Filters | NotGiven = NOT_GIVEN, - include_summary: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - only_matching_chunks: bool | NotGiven = NOT_GIVEN, - rerank: bool | NotGiven = NOT_GIVEN, - rewrite_query: bool | NotGiven = NOT_GIVEN, - user_id: str | NotGiven = NOT_GIVEN, - # 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, - ) -> SearchExecuteResponse: - """ - Search through documents with filtering - - Args: - q: Search query string - - categories_filter: Optional category filters - - chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most - chunks, more results), 1 is most sensitive (returns lesser chunks, accurate - results) - - doc_id: Optional document ID to search within. You can use this to find chunks in a very - large document. - - document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns - most documents, more results), 1 is most sensitive (returns lesser documents, - accurate results) - - filters: Optional filters to apply to the search - - include_summary: If true, include document summary in the response. This is helpful if you want a - chatbot to know the full context of the document. - - limit: Maximum number of results to return - - only_matching_chunks: If true, only return matching chunks without context. Normally, we send the - previous and next chunk to provide more context for LLMs. If you only want the - matching chunk, set this to true. - - rerank: If true, rerank the results based on the query. This is helpful if you want to - ensure the most relevant results are returned. - - rewrite_query: If true, rewrites the query to make it easier to find documents. This increases - the latency by about 400ms - - user_id: End user ID this search is associated with. NOTE: This also acts as a filter for - the search. - - 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 - """ - return await self._post( - "/search", - body=await async_maybe_transform( - { - "q": q, - "categories_filter": categories_filter, - "chunk_threshold": chunk_threshold, - "doc_id": doc_id, - "document_threshold": document_threshold, - "filters": filters, - "include_summary": include_summary, - "limit": limit, - "only_matching_chunks": only_matching_chunks, - "rerank": rerank, - "rewrite_query": rewrite_query, - "user_id": user_id, - }, - search_execute_params.SearchExecuteParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=SearchExecuteResponse, - ) - - -class SearchResourceWithRawResponse: - def __init__(self, search: SearchResource) -> None: - self._search = search - - self.execute = to_raw_response_wrapper( - search.execute, - ) - - -class AsyncSearchResourceWithRawResponse: - def __init__(self, search: AsyncSearchResource) -> None: - self._search = search - - self.execute = async_to_raw_response_wrapper( - search.execute, - ) - - -class SearchResourceWithStreamingResponse: - def __init__(self, search: SearchResource) -> None: - self._search = search - - self.execute = to_streamed_response_wrapper( - search.execute, - ) - - -class AsyncSearchResourceWithStreamingResponse: - def __init__(self, search: AsyncSearchResource) -> None: - self._search = search - - self.execute = async_to_streamed_response_wrapper( - search.execute, - ) diff --git a/src/supermemory/resources/settings.py b/src/supermemory/resources/settings.py index 63b3604e..33a331fa 100644 --- a/src/supermemory/resources/settings.py +++ b/src/supermemory/resources/settings.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import List, Iterable +from typing import Dict, List import httpx @@ -18,6 +18,7 @@ async_to_streamed_response_wrapper, ) from .._base_client import make_request_options +from ..types.setting_get_response import SettingGetResponse from ..types.setting_update_response import SettingUpdateResponse __all__ = ["SettingsResource", "AsyncSettingsResource"] @@ -46,10 +47,9 @@ def with_streaming_response(self) -> SettingsResourceWithStreamingResponse: def update( self, *, - categories: List[str] | NotGiven = NOT_GIVEN, exclude_items: List[str] | NotGiven = NOT_GIVEN, filter_prompt: str | NotGiven = NOT_GIVEN, - filter_tags: Iterable[setting_update_params.FilterTag] | NotGiven = NOT_GIVEN, + filter_tags: Dict[str, List[str]] | NotGiven = NOT_GIVEN, include_items: List[str] | NotGiven = NOT_GIVEN, should_llm_filter: bool | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -71,11 +71,10 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ - return self._put( - "/settings", + return self._patch( + "/v3/settings", body=maybe_transform( { - "categories": categories, "exclude_items": exclude_items, "filter_prompt": filter_prompt, "filter_tags": filter_tags, @@ -90,6 +89,25 @@ def update( cast_to=SettingUpdateResponse, ) + def get( + self, + *, + # 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, + ) -> SettingGetResponse: + """Get settings for an organization""" + return self._get( + "/v3/settings", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SettingGetResponse, + ) + class AsyncSettingsResource(AsyncAPIResource): @cached_property @@ -114,10 +132,9 @@ def with_streaming_response(self) -> AsyncSettingsResourceWithStreamingResponse: async def update( self, *, - categories: List[str] | NotGiven = NOT_GIVEN, exclude_items: List[str] | NotGiven = NOT_GIVEN, filter_prompt: str | NotGiven = NOT_GIVEN, - filter_tags: Iterable[setting_update_params.FilterTag] | NotGiven = NOT_GIVEN, + filter_tags: Dict[str, List[str]] | NotGiven = NOT_GIVEN, include_items: List[str] | NotGiven = NOT_GIVEN, should_llm_filter: bool | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -139,11 +156,10 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._put( - "/settings", + return await self._patch( + "/v3/settings", body=await async_maybe_transform( { - "categories": categories, "exclude_items": exclude_items, "filter_prompt": filter_prompt, "filter_tags": filter_tags, @@ -158,6 +174,25 @@ async def update( cast_to=SettingUpdateResponse, ) + async def get( + self, + *, + # 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, + ) -> SettingGetResponse: + """Get settings for an organization""" + return await self._get( + "/v3/settings", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=SettingGetResponse, + ) + class SettingsResourceWithRawResponse: def __init__(self, settings: SettingsResource) -> None: @@ -166,6 +201,9 @@ def __init__(self, settings: SettingsResource) -> None: self.update = to_raw_response_wrapper( settings.update, ) + self.get = to_raw_response_wrapper( + settings.get, + ) class AsyncSettingsResourceWithRawResponse: @@ -175,6 +213,9 @@ def __init__(self, settings: AsyncSettingsResource) -> None: self.update = async_to_raw_response_wrapper( settings.update, ) + self.get = async_to_raw_response_wrapper( + settings.get, + ) class SettingsResourceWithStreamingResponse: @@ -184,6 +225,9 @@ def __init__(self, settings: SettingsResource) -> None: self.update = to_streamed_response_wrapper( settings.update, ) + self.get = to_streamed_response_wrapper( + settings.get, + ) class AsyncSettingsResourceWithStreamingResponse: @@ -193,3 +237,6 @@ def __init__(self, settings: AsyncSettingsResource) -> None: self.update = async_to_streamed_response_wrapper( settings.update, ) + self.get = async_to_streamed_response_wrapper( + settings.get, + ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 675c5525..c7f47386 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -2,15 +2,14 @@ from __future__ import annotations -from .memory_list_params import MemoryListParams as MemoryListParams +from .memory_add_params import MemoryAddParams as MemoryAddParams +from .memory_add_response import MemoryAddResponse as MemoryAddResponse from .memory_get_response import MemoryGetResponse as MemoryGetResponse -from .memory_create_params import MemoryCreateParams as MemoryCreateParams from .memory_list_response import MemoryListResponse as MemoryListResponse -from .search_execute_params import SearchExecuteParams as SearchExecuteParams +from .setting_get_response import SettingGetResponse as SettingGetResponse from .setting_update_params import SettingUpdateParams as SettingUpdateParams -from .memory_create_response import MemoryCreateResponse as MemoryCreateResponse from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse -from .search_execute_response import SearchExecuteResponse as SearchExecuteResponse +from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse -from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams +from .connection_list_response import ConnectionListResponse as ConnectionListResponse from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse diff --git a/src/supermemory/types/connection_create_params.py b/src/supermemory/types/connection_create_params.py deleted file mode 100644 index 4bb84cba..00000000 --- a/src/supermemory/types/connection_create_params.py +++ /dev/null @@ -1,15 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["ConnectionCreateParams"] - - -class ConnectionCreateParams(TypedDict, total=False): - id: Required[str] - - redirect_url: Annotated[str, PropertyInfo(alias="redirectUrl")] diff --git a/src/supermemory/types/connection_get_response.py b/src/supermemory/types/connection_get_response.py new file mode 100644 index 00000000..8f3be8de --- /dev/null +++ b/src/supermemory/types/connection_get_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["ConnectionGetResponse"] + + +class ConnectionGetResponse(BaseModel): + id: str + + created_at: float = FieldInfo(alias="createdAt") + + provider: str + + expires_at: Optional[float] = FieldInfo(alias="expiresAt", default=None) + + metadata: Optional[Dict[str, object]] = None diff --git a/src/supermemory/types/connection_list_response.py b/src/supermemory/types/connection_list_response.py new file mode 100644 index 00000000..f68e38b8 --- /dev/null +++ b/src/supermemory/types/connection_list_response.py @@ -0,0 +1,25 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from typing_extensions import TypeAlias + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["ConnectionListResponse", "ConnectionListResponseItem"] + + +class ConnectionListResponseItem(BaseModel): + id: str + + created_at: float = FieldInfo(alias="createdAt") + + provider: str + + expires_at: Optional[float] = FieldInfo(alias="expiresAt", default=None) + + metadata: Optional[Dict[str, object]] = None + + +ConnectionListResponse: TypeAlias = List[ConnectionListResponseItem] diff --git a/src/supermemory/types/memory_add_params.py b/src/supermemory/types/memory_add_params.py new file mode 100644 index 00000000..a973e952 --- /dev/null +++ b/src/supermemory/types/memory_add_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["MemoryAddParams"] + + +class MemoryAddParams(TypedDict, total=False): + content: Required[str] + + container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + + metadata: Dict[str, Union[str, float, bool]] diff --git a/src/supermemory/types/memory_create_response.py b/src/supermemory/types/memory_add_response.py similarity index 67% rename from src/supermemory/types/memory_create_response.py rename to src/supermemory/types/memory_add_response.py index 11751fe3..704918e4 100644 --- a/src/supermemory/types/memory_create_response.py +++ b/src/supermemory/types/memory_add_response.py @@ -2,10 +2,10 @@ from .._models import BaseModel -__all__ = ["MemoryCreateResponse"] +__all__ = ["MemoryAddResponse"] -class MemoryCreateResponse(BaseModel): +class MemoryAddResponse(BaseModel): id: str status: str diff --git a/src/supermemory/types/memory_create_params.py b/src/supermemory/types/memory_create_params.py deleted file mode 100644 index 938ba8e0..00000000 --- a/src/supermemory/types/memory_create_params.py +++ /dev/null @@ -1,42 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, Union -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["MemoryCreateParams"] - - -class MemoryCreateParams(TypedDict, total=False): - content: Required[str] - """The content to extract and process into a memory. - - This can be a URL to a website, a PDF, an image, or a video. - - Plaintext: Any plaintext format - - URL: A URL to a website, PDF, image, or video - - We automatically detect the content type from the url's response format. - """ - - id: str - - metadata: Dict[str, Union[str, float, bool]] - """Optional metadata for the memory. - - This is used to store additional information about the memory. You can use this - to store any additional information you need about the memory. Metadata can be - filtered through. Keys must be strings and are case sensitive. Values can be - strings, numbers, or booleans. You cannot nest objects. - """ - - user_id: Annotated[str, PropertyInfo(alias="userId")] - """Optional end user ID this memory belongs to. - - This is used to group memories by user. You should use the same ID stored in - your external system where the user is stored - """ diff --git a/src/supermemory/types/memory_delete_response.py b/src/supermemory/types/memory_delete_response.py index 0f17a7e1..fe31c22e 100644 --- a/src/supermemory/types/memory_delete_response.py +++ b/src/supermemory/types/memory_delete_response.py @@ -6,4 +6,6 @@ class MemoryDeleteResponse(BaseModel): - success: bool + id: str + + status: str diff --git a/src/supermemory/types/memory_get_response.py b/src/supermemory/types/memory_get_response.py index a2f008e6..4d115240 100644 --- a/src/supermemory/types/memory_get_response.py +++ b/src/supermemory/types/memory_get_response.py @@ -1,36 +1,11 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - from .._models import BaseModel -__all__ = ["MemoryGetResponse", "Doc"] - - -class Doc(BaseModel): - created_at: datetime = FieldInfo(alias="createdAt") - """Timestamp when the memory was created""" - - updated_at: datetime = FieldInfo(alias="updatedAt") - """Timestamp when the memory was last updated""" - - metadata: Optional[Dict[str, object]] = None - """Custom metadata associated with the memory""" - - summary: Optional[str] = None - """Summary of the memory content""" - - title: Optional[str] = None - """Title of the memory""" +__all__ = ["MemoryGetResponse"] class MemoryGetResponse(BaseModel): - doc: Doc - """Memory document details""" + id: str - status: Optional[Literal["queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"]] = None - """Current processing status of the memory""" + status: str diff --git a/src/supermemory/types/memory_list_params.py b/src/supermemory/types/memory_list_params.py deleted file mode 100644 index d40690da..00000000 --- a/src/supermemory/types/memory_list_params.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypedDict - -__all__ = ["MemoryListParams"] - - -class MemoryListParams(TypedDict, total=False): - filters: str - """Optional filters to apply to the search""" - - limit: str - """Number of items per page""" - - order: Literal["asc", "desc"] - """Sort order""" - - page: str - """Page number to fetch""" - - sort: Literal["createdAt", "updatedAt"] - """Field to sort by""" diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index 06fff6b5..0fecfe23 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -1,60 +1,9 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - from .._models import BaseModel -__all__ = ["MemoryListResponse", "Memory", "Pagination"] - - -class Memory(BaseModel): - id: str - """Unique identifier of the memory. - - This is your user's id in your system or database. - """ - - content: Optional[str] = None - """Content of the memory""" - - created_at: datetime = FieldInfo(alias="createdAt") - """Creation timestamp""" - - metadata: Dict[str, object] - """Custom metadata associated with the memory""" - - status: Optional[Literal["queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"]] = None - """Processing status of the memory""" - - summary: Optional[str] = None - """Summary of the memory content""" - - title: str - """Title of the memory""" - - updated_at: datetime = FieldInfo(alias="updatedAt") - """Last update timestamp""" - - url: Optional[str] = None - """Source URL of the memory""" - - -class Pagination(BaseModel): - current_page: float = FieldInfo(alias="currentPage") - - limit: float - - total_items: float = FieldInfo(alias="totalItems") - - total_pages: float = FieldInfo(alias="totalPages") +__all__ = ["MemoryListResponse"] class MemoryListResponse(BaseModel): - memories: List[Memory] - - pagination: Pagination - """Pagination metadata""" + success: bool diff --git a/src/supermemory/types/search_execute_params.py b/src/supermemory/types/search_execute_params.py deleted file mode 100644 index bc3b4756..00000000 --- a/src/supermemory/types/search_execute_params.py +++ /dev/null @@ -1,86 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, List, Union, Iterable -from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["SearchExecuteParams", "Filters", "FiltersUnionMember0"] - - -class SearchExecuteParams(TypedDict, total=False): - q: Required[str] - """Search query string""" - - categories_filter: Annotated[ - List[Literal["technology", "science", "business", "health"]], PropertyInfo(alias="categoriesFilter") - ] - """Optional category filters""" - - chunk_threshold: Annotated[float, PropertyInfo(alias="chunkThreshold")] - """Threshold / sensitivity for chunk selection. - - 0 is least sensitive (returns most chunks, more results), 1 is most sensitive - (returns lesser chunks, accurate results) - """ - - doc_id: Annotated[str, PropertyInfo(alias="docId")] - """Optional document ID to search within. - - You can use this to find chunks in a very large document. - """ - - document_threshold: Annotated[float, PropertyInfo(alias="documentThreshold")] - """Threshold / sensitivity for document selection. - - 0 is least sensitive (returns most documents, more results), 1 is most sensitive - (returns lesser documents, accurate results) - """ - - filters: Filters - """Optional filters to apply to the search""" - - include_summary: Annotated[bool, PropertyInfo(alias="includeSummary")] - """If true, include document summary in the response. - - This is helpful if you want a chatbot to know the full context of the document. - """ - - limit: int - """Maximum number of results to return""" - - only_matching_chunks: Annotated[bool, PropertyInfo(alias="onlyMatchingChunks")] - """If true, only return matching chunks without context. - - Normally, we send the previous and next chunk to provide more context for LLMs. - If you only want the matching chunk, set this to true. - """ - - rerank: bool - """If true, rerank the results based on the query. - - This is helpful if you want to ensure the most relevant results are returned. - """ - - rewrite_query: Annotated[bool, PropertyInfo(alias="rewriteQuery")] - """If true, rewrites the query to make it easier to find documents. - - This increases the latency by about 400ms - """ - - user_id: Annotated[str, PropertyInfo(alias="userId")] - """End user ID this search is associated with. - - NOTE: This also acts as a filter for the search. - """ - - -class FiltersUnionMember0(TypedDict, total=False): - and_: Annotated[Iterable[object], PropertyInfo(alias="AND")] - - or_: Annotated[Iterable[object], PropertyInfo(alias="OR")] - - -Filters: TypeAlias = Union[FiltersUnionMember0, Dict[str, object]] diff --git a/src/supermemory/types/search_execute_response.py b/src/supermemory/types/search_execute_response.py deleted file mode 100644 index 5b2bc4e0..00000000 --- a/src/supermemory/types/search_execute_response.py +++ /dev/null @@ -1,52 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["SearchExecuteResponse", "Result", "ResultChunk"] - - -class ResultChunk(BaseModel): - content: str - """Content of the matching chunk""" - - is_relevant: bool = FieldInfo(alias="isRelevant") - """Whether this chunk is relevant to the query""" - - score: float - """Similarity score for this chunk""" - - -class Result(BaseModel): - chunks: List[ResultChunk] - """Matching content chunks from the document""" - - created_at: datetime = FieldInfo(alias="createdAt") - """Document creation date""" - - document_id: str = FieldInfo(alias="documentId") - """ID of the matching document""" - - metadata: Optional[Dict[str, object]] = None - """Document metadata""" - - score: float - """Relevance score of the match""" - - title: str - """Document title""" - - updated_at: datetime = FieldInfo(alias="updatedAt") - """Document last update date""" - - -class SearchExecuteResponse(BaseModel): - results: List[Result] - - timing: float - - total: float diff --git a/src/supermemory/types/setting_get_response.py b/src/supermemory/types/setting_get_response.py new file mode 100644 index 00000000..0cf25fd9 --- /dev/null +++ b/src/supermemory/types/setting_get_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from .._models import BaseModel + +__all__ = ["SettingGetResponse"] + + +class SettingGetResponse(BaseModel): + settings: Dict[str, object] diff --git a/src/supermemory/types/setting_update_params.py b/src/supermemory/types/setting_update_params.py index 16775c4c..8f19a083 100644 --- a/src/supermemory/types/setting_update_params.py +++ b/src/supermemory/types/setting_update_params.py @@ -2,29 +2,21 @@ from __future__ import annotations -from typing import List, Iterable -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict, List +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo -__all__ = ["SettingUpdateParams", "FilterTag"] +__all__ = ["SettingUpdateParams"] class SettingUpdateParams(TypedDict, total=False): - categories: List[str] - exclude_items: Annotated[List[str], PropertyInfo(alias="excludeItems")] filter_prompt: Annotated[str, PropertyInfo(alias="filterPrompt")] - filter_tags: Annotated[Iterable[FilterTag], PropertyInfo(alias="filterTags")] + filter_tags: Annotated[Dict[str, List[str]], PropertyInfo(alias="filterTags")] include_items: Annotated[List[str], PropertyInfo(alias="includeItems")] should_llm_filter: Annotated[bool, PropertyInfo(alias="shouldLLMFilter")] - - -class FilterTag(TypedDict, total=False): - score: Required[float] - - tag: Required[str] diff --git a/src/supermemory/types/setting_update_response.py b/src/supermemory/types/setting_update_response.py index 064e3a61..58a717c0 100644 --- a/src/supermemory/types/setting_update_response.py +++ b/src/supermemory/types/setting_update_response.py @@ -1,28 +1,20 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import List, Optional +from typing import Dict, List, Optional from pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["SettingUpdateResponse", "Settings", "SettingsFilterTag"] - - -class SettingsFilterTag(BaseModel): - score: float - - tag: str +__all__ = ["SettingUpdateResponse", "Settings"] class Settings(BaseModel): - categories: Optional[List[str]] = None - exclude_items: Optional[List[str]] = FieldInfo(alias="excludeItems", default=None) filter_prompt: Optional[str] = FieldInfo(alias="filterPrompt", default=None) - filter_tags: Optional[List[SettingsFilterTag]] = FieldInfo(alias="filterTags", default=None) + filter_tags: Optional[Dict[str, List[str]]] = FieldInfo(alias="filterTags", default=None) include_items: Optional[List[str]] = FieldInfo(alias="includeItems", default=None) diff --git a/tests/api_resources/test_connection.py b/tests/api_resources/test_connection.py deleted file mode 100644 index cd9555e8..00000000 --- a/tests/api_resources/test_connection.py +++ /dev/null @@ -1,200 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from supermemory import Supermemory, AsyncSupermemory -from tests.utils import assert_matches_type -from supermemory.types import ConnectionCreateResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestConnection: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_create(self, client: Supermemory) -> None: - connection = client.connection.create( - app="notion", - id="id", - ) - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_create_with_all_params(self, client: Supermemory) -> None: - connection = client.connection.create( - app="notion", - id="id", - redirect_url="redirectUrl", - ) - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_create(self, client: Supermemory) -> None: - response = client.connection.with_raw_response.create( - app="notion", - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - connection = response.parse() - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_create(self, client: Supermemory) -> None: - with client.connection.with_streaming_response.create( - app="notion", - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - connection = response.parse() - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_method_retrieve(self, client: Supermemory) -> None: - connection = client.connection.retrieve( - "connectionId", - ) - assert connection is None - - @pytest.mark.skip() - @parametrize - def test_raw_response_retrieve(self, client: Supermemory) -> None: - response = client.connection.with_raw_response.retrieve( - "connectionId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - connection = response.parse() - assert connection is None - - @pytest.mark.skip() - @parametrize - def test_streaming_response_retrieve(self, client: Supermemory) -> None: - with client.connection.with_streaming_response.retrieve( - "connectionId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - connection = response.parse() - assert connection is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_retrieve(self, client: Supermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `connection_id` but received ''"): - client.connection.with_raw_response.retrieve( - "", - ) - - -class TestAsyncConnection: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - async def test_method_create(self, async_client: AsyncSupermemory) -> None: - connection = await async_client.connection.create( - app="notion", - id="id", - ) - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncSupermemory) -> None: - connection = await async_client.connection.create( - app="notion", - id="id", - redirect_url="redirectUrl", - ) - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None: - response = await async_client.connection.with_raw_response.create( - app="notion", - id="id", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - connection = await response.parse() - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_create(self, async_client: AsyncSupermemory) -> None: - async with async_client.connection.with_streaming_response.create( - app="notion", - id="id", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - connection = await response.parse() - assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_method_retrieve(self, async_client: AsyncSupermemory) -> None: - connection = await async_client.connection.retrieve( - "connectionId", - ) - assert connection is None - - @pytest.mark.skip() - @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncSupermemory) -> None: - response = await async_client.connection.with_raw_response.retrieve( - "connectionId", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - connection = await response.parse() - assert connection is None - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncSupermemory) -> None: - async with async_client.connection.with_streaming_response.retrieve( - "connectionId", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - connection = await response.parse() - assert connection is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_retrieve(self, async_client: AsyncSupermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `connection_id` but received ''"): - await async_client.connection.with_raw_response.retrieve( - "", - ) diff --git a/tests/api_resources/test_connections.py b/tests/api_resources/test_connections.py new file mode 100644 index 00000000..4a34deed --- /dev/null +++ b/tests/api_resources/test_connections.py @@ -0,0 +1,230 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from supermemory import Supermemory, AsyncSupermemory +from tests.utils import assert_matches_type +from supermemory.types import ConnectionGetResponse, ConnectionListResponse, ConnectionCreateResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestConnections: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_create(self, client: Supermemory) -> None: + connection = client.connections.create( + "notion", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Supermemory) -> None: + response = client.connections.with_raw_response.create( + "notion", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_create(self, client: Supermemory) -> None: + with client.connections.with_streaming_response.create( + "notion", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_list(self, client: Supermemory) -> None: + connection = client.connections.list() + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_list(self, client: Supermemory) -> None: + response = client.connections.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_list(self, client: Supermemory) -> None: + with client.connections.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_method_get(self, client: Supermemory) -> None: + connection = client.connections.get( + "connectionId", + ) + assert_matches_type(ConnectionGetResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_get(self, client: Supermemory) -> None: + response = client.connections.with_raw_response.get( + "connectionId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = response.parse() + assert_matches_type(ConnectionGetResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_get(self, client: Supermemory) -> None: + with client.connections.with_streaming_response.get( + "connectionId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = response.parse() + assert_matches_type(ConnectionGetResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_get(self, client: Supermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connection_id` but received ''"): + client.connections.with_raw_response.get( + "", + ) + + +class TestAsyncConnections: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create(self, async_client: AsyncSupermemory) -> None: + connection = await async_client.connections.create( + "notion", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None: + response = await async_client.connections.with_raw_response.create( + "notion", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_create(self, async_client: AsyncSupermemory) -> None: + async with async_client.connections.with_streaming_response.create( + "notion", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_list(self, async_client: AsyncSupermemory) -> None: + connection = await async_client.connections.list() + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: + response = await async_client.connections.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> None: + async with async_client.connections.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_get(self, async_client: AsyncSupermemory) -> None: + connection = await async_client.connections.get( + "connectionId", + ) + assert_matches_type(ConnectionGetResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_get(self, async_client: AsyncSupermemory) -> None: + response = await async_client.connections.with_raw_response.get( + "connectionId", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + connection = await response.parse() + assert_matches_type(ConnectionGetResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_get(self, async_client: AsyncSupermemory) -> None: + async with async_client.connections.with_streaming_response.get( + "connectionId", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + connection = await response.parse() + assert_matches_type(ConnectionGetResponse, connection, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_get(self, async_client: AsyncSupermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `connection_id` but received ''"): + await async_client.connections.with_raw_response.get( + "", + ) diff --git a/tests/api_resources/test_memory.py b/tests/api_resources/test_memories.py similarity index 70% rename from tests/api_resources/test_memory.py rename to tests/api_resources/test_memories.py index c41dde7b..304e16ec 100644 --- a/tests/api_resources/test_memory.py +++ b/tests/api_resources/test_memories.py @@ -10,156 +10,157 @@ from supermemory import Supermemory, AsyncSupermemory from tests.utils import assert_matches_type from supermemory.types import ( + MemoryAddResponse, MemoryGetResponse, MemoryListResponse, - MemoryCreateResponse, MemoryDeleteResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -class TestMemory: +class TestMemories: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip() @parametrize - def test_method_create(self, client: Supermemory) -> None: - memory = client.memory.create( - content="This is a detailed article about machine learning concepts..", - ) - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_create_with_all_params(self, client: Supermemory) -> None: - memory = client.memory.create( - content="This is a detailed article about machine learning concepts..", - id="id", - metadata={ - "source": "web", - "category": "technology", - "tag_1": "ai", - "tag_2": "machine-learning", - "readingTime": 5, - "isPublic": True, - }, - user_id="user_123", + def test_method_list(self, client: Supermemory) -> None: + memory = client.memories.list( + "id", ) - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryListResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_raw_response_create(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", + def test_raw_response_list(self, client: Supermemory) -> None: + response = client.memories.with_raw_response.list( + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryListResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_streaming_response_create(self, client: Supermemory) -> None: - with client.memory.with_streaming_response.create( - content="This is a detailed article about machine learning concepts..", + def test_streaming_response_list(self, client: Supermemory) -> None: + with client.memories.with_streaming_response.list( + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryListResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize - def test_method_list(self, client: Supermemory) -> None: - memory = client.memory.list() - assert_matches_type(MemoryListResponse, memory, path=["response"]) + def test_path_params_list(self, client: Supermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.memories.with_raw_response.list( + "", + ) @pytest.mark.skip() @parametrize - def test_method_list_with_all_params(self, client: Supermemory) -> None: - memory = client.memory.list( - filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', - limit="10", - order="desc", - page="1", - sort="createdAt", + def test_method_delete(self, client: Supermemory) -> None: + memory = client.memories.delete( + "id", ) - assert_matches_type(MemoryListResponse, memory, path=["response"]) + assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_raw_response_list(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.list() + def test_raw_response_delete(self, client: Supermemory) -> None: + response = client.memories.with_raw_response.delete( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) + assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_streaming_response_list(self, client: Supermemory) -> None: - with client.memory.with_streaming_response.list() as response: + def test_streaming_response_delete(self, client: Supermemory) -> None: + with client.memories.with_streaming_response.delete( + "id", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) + assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize - def test_method_delete(self, client: Supermemory) -> None: - memory = client.memory.delete( - "id", + def test_path_params_delete(self, client: Supermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.memories.with_raw_response.delete( + "", + ) + + @pytest.mark.skip() + @parametrize + def test_method_add(self, client: Supermemory) -> None: + memory = client.memories.add( + content="This is a detailed article about machine learning concepts...", ) - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert_matches_type(MemoryAddResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_raw_response_delete(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.delete( - "id", + def test_method_add_with_all_params(self, client: Supermemory) -> None: + memory = client.memories.add( + content="This is a detailed article about machine learning concepts...", + container_tags=["string"], + metadata={ + "source": "web", + "category": "technology", + "tag_1": "ai", + "tag_2": "machine-learning", + "readingTime": 5, + "isPublic": True, + }, + ) + assert_matches_type(MemoryAddResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_add(self, client: Supermemory) -> None: + response = client.memories.with_raw_response.add( + content="This is a detailed article about machine learning concepts...", ) 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"]) + assert_matches_type(MemoryAddResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_streaming_response_delete(self, client: Supermemory) -> None: - with client.memory.with_streaming_response.delete( - "id", + def test_streaming_response_add(self, client: Supermemory) -> None: + with client.memories.with_streaming_response.add( + content="This is a detailed article about machine learning concepts...", ) 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_matches_type(MemoryAddResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True - @pytest.mark.skip() - @parametrize - def test_path_params_delete(self, client: Supermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.memory.with_raw_response.delete( - "", - ) - @pytest.mark.skip() @parametrize def test_method_get(self, client: Supermemory) -> None: - memory = client.memory.get( + memory = client.memories.get( "id", ) assert_matches_type(MemoryGetResponse, memory, path=["response"]) @@ -167,7 +168,7 @@ def test_method_get(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_raw_response_get(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.get( + response = client.memories.with_raw_response.get( "id", ) @@ -179,7 +180,7 @@ def test_raw_response_get(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_get(self, client: Supermemory) -> None: - with client.memory.with_streaming_response.get( + with client.memories.with_streaming_response.get( "id", ) as response: assert not response.is_closed @@ -194,152 +195,153 @@ def test_streaming_response_get(self, client: Supermemory) -> None: @parametrize def test_path_params_get(self, client: Supermemory) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.memory.with_raw_response.get( + client.memories.with_raw_response.get( "", ) -class TestAsyncMemory: +class TestAsyncMemories: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip() @parametrize - async def test_method_create(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.create( - content="This is a detailed article about machine learning concepts..", - ) - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.create( - content="This is a detailed article about machine learning concepts..", - id="id", - metadata={ - "source": "web", - "category": "technology", - "tag_1": "ai", - "tag_2": "machine-learning", - "readingTime": 5, - "isPublic": True, - }, - user_id="user_123", + async def test_method_list(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.list( + "id", ) - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryListResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", + async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: + response = await async_client.memories.with_raw_response.list( + "id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryListResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - async def test_streaming_response_create(self, async_client: AsyncSupermemory) -> None: - async with async_client.memory.with_streaming_response.create( - content="This is a detailed article about machine learning concepts..", + async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> None: + async with async_client.memories.with_streaming_response.list( + "id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryListResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize - async def test_method_list(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.list() - assert_matches_type(MemoryListResponse, memory, path=["response"]) + async def test_path_params_list(self, async_client: AsyncSupermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.memories.with_raw_response.list( + "", + ) @pytest.mark.skip() @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.list( - filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', - limit="10", - order="desc", - page="1", - sort="createdAt", + async def test_method_delete(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.delete( + "id", ) - assert_matches_type(MemoryListResponse, memory, path=["response"]) + assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memory.with_raw_response.list() + async def test_raw_response_delete(self, async_client: AsyncSupermemory) -> None: + response = await async_client.memories.with_raw_response.delete( + "id", + ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) + assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> None: - async with async_client.memory.with_streaming_response.list() as response: + async def test_streaming_response_delete(self, async_client: AsyncSupermemory) -> None: + async with async_client.memories.with_streaming_response.delete( + "id", + ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) + assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip() @parametrize - async def test_method_delete(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.delete( - "id", + async def test_path_params_delete(self, async_client: AsyncSupermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.memories.with_raw_response.delete( + "", + ) + + @pytest.mark.skip() + @parametrize + async def test_method_add(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.add( + content="This is a detailed article about machine learning concepts...", ) - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert_matches_type(MemoryAddResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - async def test_raw_response_delete(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memory.with_raw_response.delete( - "id", + async def test_method_add_with_all_params(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.add( + content="This is a detailed article about machine learning concepts...", + container_tags=["string"], + metadata={ + "source": "web", + "category": "technology", + "tag_1": "ai", + "tag_2": "machine-learning", + "readingTime": 5, + "isPublic": True, + }, + ) + assert_matches_type(MemoryAddResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_add(self, async_client: AsyncSupermemory) -> None: + response = await async_client.memories.with_raw_response.add( + content="This is a detailed article about machine learning concepts...", ) 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"]) + assert_matches_type(MemoryAddResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - async def test_streaming_response_delete(self, async_client: AsyncSupermemory) -> None: - async with async_client.memory.with_streaming_response.delete( - "id", + async def test_streaming_response_add(self, async_client: AsyncSupermemory) -> None: + async with async_client.memories.with_streaming_response.add( + content="This is a detailed article about machine learning concepts...", ) 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_matches_type(MemoryAddResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True - @pytest.mark.skip() - @parametrize - async def test_path_params_delete(self, async_client: AsyncSupermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.memory.with_raw_response.delete( - "", - ) - @pytest.mark.skip() @parametrize async def test_method_get(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.get( + memory = await async_client.memories.get( "id", ) assert_matches_type(MemoryGetResponse, memory, path=["response"]) @@ -347,7 +349,7 @@ async def test_method_get(self, async_client: AsyncSupermemory) -> None: @pytest.mark.skip() @parametrize async def test_raw_response_get(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memory.with_raw_response.get( + response = await async_client.memories.with_raw_response.get( "id", ) @@ -359,7 +361,7 @@ async def test_raw_response_get(self, async_client: AsyncSupermemory) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_get(self, async_client: AsyncSupermemory) -> None: - async with async_client.memory.with_streaming_response.get( + async with async_client.memories.with_streaming_response.get( "id", ) as response: assert not response.is_closed @@ -374,6 +376,6 @@ async def test_streaming_response_get(self, async_client: AsyncSupermemory) -> N @parametrize async def test_path_params_get(self, async_client: AsyncSupermemory) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.memory.with_raw_response.get( + await async_client.memories.with_raw_response.get( "", ) diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py deleted file mode 100644 index c0557ff5..00000000 --- a/tests/api_resources/test_search.py +++ /dev/null @@ -1,160 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from supermemory import Supermemory, AsyncSupermemory -from tests.utils import assert_matches_type -from supermemory.types import SearchExecuteResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestSearch: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_execute(self, client: Supermemory) -> None: - search = client.search.execute( - q="machine learning concepts", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_execute_with_all_params(self, client: Supermemory) -> None: - search = client.search.execute( - q="machine learning concepts", - categories_filter=["technology", "science"], - chunk_threshold=0.5, - doc_id="doc_xyz789", - document_threshold=0.5, - filters={ - "and_": [ - { - "key": "group", - "value": "jira_users", - "negate": False, - }, - { - "filterType": "numeric", - "key": "timestamp", - "value": "1742745777", - "negate": False, - "numericOperator": ">", - }, - ], - "or_": [{}], - }, - include_summary=False, - limit=10, - only_matching_chunks=False, - rerank=False, - rewrite_query=False, - user_id="user_123", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_execute(self, client: Supermemory) -> None: - response = client.search.with_raw_response.execute( - q="machine learning concepts", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - search = response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_execute(self, client: Supermemory) -> None: - with client.search.with_streaming_response.execute( - q="machine learning concepts", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - search = response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncSearch: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - async def test_method_execute(self, async_client: AsyncSupermemory) -> None: - search = await async_client.search.execute( - q="machine learning concepts", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_execute_with_all_params(self, async_client: AsyncSupermemory) -> None: - search = await async_client.search.execute( - q="machine learning concepts", - categories_filter=["technology", "science"], - chunk_threshold=0.5, - doc_id="doc_xyz789", - document_threshold=0.5, - filters={ - "and_": [ - { - "key": "group", - "value": "jira_users", - "negate": False, - }, - { - "filterType": "numeric", - "key": "timestamp", - "value": "1742745777", - "negate": False, - "numericOperator": ">", - }, - ], - "or_": [{}], - }, - include_summary=False, - limit=10, - only_matching_chunks=False, - rerank=False, - rewrite_query=False, - user_id="user_123", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_execute(self, async_client: AsyncSupermemory) -> None: - response = await async_client.search.with_raw_response.execute( - q="machine learning concepts", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - search = await response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_execute(self, async_client: AsyncSupermemory) -> None: - async with async_client.search.with_streaming_response.execute( - q="machine learning concepts", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - search = await response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_settings.py b/tests/api_resources/test_settings.py index 78bf0968..a7a8a777 100644 --- a/tests/api_resources/test_settings.py +++ b/tests/api_resources/test_settings.py @@ -9,7 +9,7 @@ from supermemory import Supermemory, AsyncSupermemory from tests.utils import assert_matches_type -from supermemory.types import SettingUpdateResponse +from supermemory.types import SettingGetResponse, SettingUpdateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -27,15 +27,9 @@ def test_method_update(self, client: Supermemory) -> None: @parametrize def test_method_update_with_all_params(self, client: Supermemory) -> None: setting = client.settings.update( - categories=["x"], exclude_items=["x"], filter_prompt="x", - filter_tags=[ - { - "score": 0, - "tag": "x", - } - ], + filter_tags={"foo": ["string"]}, include_items=["x"], should_llm_filter=True, ) @@ -63,6 +57,34 @@ def test_streaming_response_update(self, client: Supermemory) -> None: assert cast(Any, response.is_closed) is True + @pytest.mark.skip() + @parametrize + def test_method_get(self, client: Supermemory) -> None: + setting = client.settings.get() + assert_matches_type(SettingGetResponse, setting, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_get(self, client: Supermemory) -> None: + response = client.settings.with_raw_response.get() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + setting = response.parse() + assert_matches_type(SettingGetResponse, setting, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_get(self, client: Supermemory) -> None: + with client.settings.with_streaming_response.get() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + setting = response.parse() + assert_matches_type(SettingGetResponse, setting, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncSettings: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -77,15 +99,9 @@ async def test_method_update(self, async_client: AsyncSupermemory) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncSupermemory) -> None: setting = await async_client.settings.update( - categories=["x"], exclude_items=["x"], filter_prompt="x", - filter_tags=[ - { - "score": 0, - "tag": "x", - } - ], + filter_tags={"foo": ["string"]}, include_items=["x"], should_llm_filter=True, ) @@ -112,3 +128,31 @@ async def test_streaming_response_update(self, async_client: AsyncSupermemory) - assert_matches_type(SettingUpdateResponse, setting, path=["response"]) assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_method_get(self, async_client: AsyncSupermemory) -> None: + setting = await async_client.settings.get() + assert_matches_type(SettingGetResponse, setting, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_get(self, async_client: AsyncSupermemory) -> None: + response = await async_client.settings.with_raw_response.get() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + setting = await response.parse() + assert_matches_type(SettingGetResponse, setting, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_get(self, async_client: AsyncSupermemory) -> None: + async with async_client.settings.with_streaming_response.get() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + setting = await response.parse() + assert_matches_type(SettingGetResponse, setting, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index d0f97a12..977e1f61 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,17 +23,15 @@ from supermemory import Supermemory, AsyncSupermemory, APIResponseValidationError from supermemory._types import Omit -from supermemory._utils import maybe_transform from supermemory._models import BaseModel, FinalRequestOptions from supermemory._constants import RAW_RESPONSE_HEADER -from supermemory._exceptions import APIStatusError, APITimeoutError, APIResponseValidationError +from supermemory._exceptions import APIStatusError, APITimeoutError, SupermemoryError, APIResponseValidationError from supermemory._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, make_request_options, ) -from supermemory.types.memory_create_params import MemoryCreateParams from .utils import update_env @@ -336,6 +334,16 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + def test_validate_headers(self) -> None: + client = Supermemory(base_url=base_url, api_key=api_key, _strict_response_validation=True) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert request.headers.get("Authorization") == f"Bearer {api_key}" + + with pytest.raises(SupermemoryError): + with update_env(**{"SUPERMEMORY_API_KEY": Omit()}): + client2 = Supermemory(base_url=base_url, api_key=None, _strict_response_validation=True) + _ = client2 + def test_default_query_option(self) -> None: client = Supermemory( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} @@ -714,19 +722,11 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/add").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.delete("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/add", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + self.client.delete( + "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} ) assert _get_open_connections(self.client) == 0 @@ -734,19 +734,11 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/add").mock(return_value=httpx.Response(500)) + respx_mock.delete("/v3/memories/id").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/add", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + self.client.delete( + "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} ) assert _get_open_connections(self.client) == 0 @@ -775,11 +767,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/add").mock(side_effect=retry_handler) + respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) - response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts.." - ) + response = client.memories.with_raw_response.list("id") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -801,12 +791,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/add").mock(side_effect=retry_handler) + respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) - response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", - extra_headers={"x-stainless-retry-count": Omit()}, - ) + response = client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -827,12 +814,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/add").mock(side_effect=retry_handler) + respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) - response = client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", - extra_headers={"x-stainless-retry-count": "42"}, - ) + response = client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1116,6 +1100,16 @@ def test_default_headers_option(self) -> None: assert request.headers.get("x-foo") == "stainless" assert request.headers.get("x-stainless-lang") == "my-overriding-header" + def test_validate_headers(self) -> None: + client = AsyncSupermemory(base_url=base_url, api_key=api_key, _strict_response_validation=True) + request = client._build_request(FinalRequestOptions(method="get", url="/foo")) + assert request.headers.get("Authorization") == f"Bearer {api_key}" + + with pytest.raises(SupermemoryError): + with update_env(**{"SUPERMEMORY_API_KEY": Omit()}): + client2 = AsyncSupermemory(base_url=base_url, api_key=None, _strict_response_validation=True) + _ = client2 + def test_default_query_option(self) -> None: client = AsyncSupermemory( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} @@ -1498,19 +1492,11 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/add").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.delete("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/add", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + await self.client.delete( + "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} ) assert _get_open_connections(self.client) == 0 @@ -1518,19 +1504,11 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.post("/add").mock(return_value=httpx.Response(500)) + respx_mock.delete("/v3/memories/id").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/add", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts.."), MemoryCreateParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, + await self.client.delete( + "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} ) assert _get_open_connections(self.client) == 0 @@ -1560,11 +1538,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/add").mock(side_effect=retry_handler) + respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) - response = await client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts.." - ) + response = await client.memories.with_raw_response.list("id") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1587,12 +1563,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/add").mock(side_effect=retry_handler) + respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) - response = await client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", - extra_headers={"x-stainless-retry-count": Omit()}, - ) + response = await client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1614,12 +1587,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/add").mock(side_effect=retry_handler) + respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) - response = await client.memory.with_raw_response.create( - content="This is a detailed article about machine learning concepts..", - extra_headers={"x-stainless-retry-count": "42"}, - ) + response = await client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From af34c01553feba151893eea0f6a905078146424f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 14:49:00 +0000 Subject: [PATCH 11/24] feat(api): manual updates --- .stats.yml | 6 +- README.md | 50 ++++--- api.md | 2 + src/supermemory/resources/memories.py | 107 +++++++++++++- src/supermemory/types/__init__.py | 2 + .../types/connection_create_response.py | 6 +- src/supermemory/types/memory_update_params.py | 20 +++ .../types/memory_update_response.py | 11 ++ tests/api_resources/test_memories.py | 139 ++++++++++++++++++ tests/test_client.py | 126 +++++++++++++--- 10 files changed, 421 insertions(+), 48 deletions(-) create mode 100644 src/supermemory/types/memory_update_params.py create mode 100644 src/supermemory/types/memory_update_response.py diff --git a/.stats.yml b/.stats.yml index 75349c0b..3ae21f36 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 8 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-3fa2583744becce1e91ec5ad18f45d2cf17778def3a8f70537a15b08c746c2fb.yml -openapi_spec_hash: bf3c5827e7ddb8b32435aeb671fe7845 +configured_endpoints: 9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-a280bf0a406a709c156d5e3fe7c8ae93ebe607d937af8d725d0a2997cab07a7d.yml +openapi_spec_hash: 15c602571db9cbcce4703c929b994d82 config_hash: b9c958a39a94966479e516e9061818be diff --git a/README.md b/README.md index 9f05e6a6..8bdb29a8 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,12 @@ client = Supermemory( api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted ) -memories = client.memories.list( - "id", +memory = client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) -print(memories.success) +print(memory.id) ``` While you can provide an `api_key` keyword argument, @@ -57,10 +59,12 @@ client = AsyncSupermemory( async def main() -> None: - memories = await client.memories.list( - "id", + memory = await client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) - print(memories.success) + print(memory.id) asyncio.run(main()) @@ -93,8 +97,10 @@ from supermemory import Supermemory client = Supermemory() try: - client.memories.list( - "id", + client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) except supermemory.APIConnectionError as e: print("The server could not be reached") @@ -138,8 +144,10 @@ client = Supermemory( ) # Or, configure per-request: -client.with_options(max_retries=5).memories.list( - "id", +client.with_options(max_retries=5).memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) ``` @@ -163,8 +171,10 @@ client = Supermemory( ) # Override per-request: -client.with_options(timeout=5.0).memories.list( - "id", +client.with_options(timeout=5.0).memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) ``` @@ -206,13 +216,15 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from supermemory import Supermemory client = Supermemory() -response = client.memories.with_raw_response.list( - "id", +response = client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) print(response.headers.get('X-My-Header')) -memory = response.parse() # get the object that `memories.list()` would have returned -print(memory.success) +memory = response.parse() # get the object that `memories.update()` would have returned +print(memory.id) ``` These methods return an [`APIResponse`](https://github.com/supermemoryai/python-sdk/tree/main/src/supermemory/_response.py) object. @@ -226,8 +238,10 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.memories.with_streaming_response.list( - "id", +with client.memories.with_streaming_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", ) as response: print(response.headers.get("X-My-Header")) diff --git a/api.md b/api.md index ea51d4ff..bbc056bf 100644 --- a/api.md +++ b/api.md @@ -4,6 +4,7 @@ Types: ```python from supermemory.types import ( + MemoryUpdateResponse, MemoryListResponse, MemoryDeleteResponse, MemoryAddResponse, @@ -13,6 +14,7 @@ from supermemory.types import ( Methods: +- client.memories.update(path_id, \*\*params) -> MemoryUpdateResponse - client.memories.list(id) -> MemoryListResponse - client.memories.delete(id) -> MemoryDeleteResponse - client.memories.add(\*\*params) -> MemoryAddResponse diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index 891d1b6a..59d5fbc1 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -6,7 +6,7 @@ import httpx -from ..types import memory_add_params +from ..types import memory_add_params, memory_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -22,6 +22,7 @@ from ..types.memory_get_response import MemoryGetResponse from ..types.memory_list_response import MemoryListResponse from ..types.memory_delete_response import MemoryDeleteResponse +from ..types.memory_update_response import MemoryUpdateResponse __all__ = ["MemoriesResource", "AsyncMemoriesResource"] @@ -46,6 +47,52 @@ def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: """ return MemoriesResourceWithStreamingResponse(self) + def update( + self, + path_id: str, + *, + body_id: str, + content: str, + container_tags: List[str] | NotGiven = NOT_GIVEN, + metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, + # 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, + ) -> MemoryUpdateResponse: + """ + Update a memory with any content type (text, url, file, etc.) and metadata + + 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 path_id: + raise ValueError(f"Expected a non-empty value for `path_id` but received {path_id!r}") + return self._patch( + f"/v3/memories/{path_id}", + body=maybe_transform( + { + "body_id": body_id, + "content": content, + "container_tags": container_tags, + "metadata": metadata, + }, + memory_update_params.MemoryUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryUpdateResponse, + ) + def list( self, id: str, @@ -207,6 +254,52 @@ def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: """ return AsyncMemoriesResourceWithStreamingResponse(self) + async def update( + self, + path_id: str, + *, + body_id: str, + content: str, + container_tags: List[str] | NotGiven = NOT_GIVEN, + metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, + # 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, + ) -> MemoryUpdateResponse: + """ + Update a memory with any content type (text, url, file, etc.) and metadata + + 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 path_id: + raise ValueError(f"Expected a non-empty value for `path_id` but received {path_id!r}") + return await self._patch( + f"/v3/memories/{path_id}", + body=await async_maybe_transform( + { + "body_id": body_id, + "content": content, + "container_tags": container_tags, + "metadata": metadata, + }, + memory_update_params.MemoryUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryUpdateResponse, + ) + async def list( self, id: str, @@ -352,6 +445,9 @@ class MemoriesResourceWithRawResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories + self.update = to_raw_response_wrapper( + memories.update, + ) self.list = to_raw_response_wrapper( memories.list, ) @@ -370,6 +466,9 @@ class AsyncMemoriesResourceWithRawResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories + self.update = async_to_raw_response_wrapper( + memories.update, + ) self.list = async_to_raw_response_wrapper( memories.list, ) @@ -388,6 +487,9 @@ class MemoriesResourceWithStreamingResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories + self.update = to_streamed_response_wrapper( + memories.update, + ) self.list = to_streamed_response_wrapper( memories.list, ) @@ -406,6 +508,9 @@ class AsyncMemoriesResourceWithStreamingResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories + self.update = async_to_streamed_response_wrapper( + memories.update, + ) self.list = async_to_streamed_response_wrapper( memories.list, ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index c7f47386..1dc2977e 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -6,9 +6,11 @@ from .memory_add_response import MemoryAddResponse as MemoryAddResponse from .memory_get_response import MemoryGetResponse as MemoryGetResponse from .memory_list_response import MemoryListResponse as MemoryListResponse +from .memory_update_params import MemoryUpdateParams as MemoryUpdateParams from .setting_get_response import SettingGetResponse as SettingGetResponse from .setting_update_params import SettingUpdateParams as SettingUpdateParams from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse +from .memory_update_response import MemoryUpdateResponse as MemoryUpdateResponse from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse from .connection_list_response import ConnectionListResponse as ConnectionListResponse diff --git a/src/supermemory/types/connection_create_response.py b/src/supermemory/types/connection_create_response.py index d27a6fab..d8b52e4c 100644 --- a/src/supermemory/types/connection_create_response.py +++ b/src/supermemory/types/connection_create_response.py @@ -8,6 +8,8 @@ class ConnectionCreateResponse(BaseModel): - expires_in: str = FieldInfo(alias="expiresIn") + id: str + + auth_link: str = FieldInfo(alias="authLink") - magic_link: str = FieldInfo(alias="magicLink") + expires_in: str = FieldInfo(alias="expiresIn") diff --git a/src/supermemory/types/memory_update_params.py b/src/supermemory/types/memory_update_params.py new file mode 100644 index 00000000..adceb6e0 --- /dev/null +++ b/src/supermemory/types/memory_update_params.py @@ -0,0 +1,20 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["MemoryUpdateParams"] + + +class MemoryUpdateParams(TypedDict, total=False): + body_id: Required[Annotated[str, PropertyInfo(alias="id")]] + + content: Required[str] + + container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + + metadata: Dict[str, Union[str, float, bool]] diff --git a/src/supermemory/types/memory_update_response.py b/src/supermemory/types/memory_update_response.py new file mode 100644 index 00000000..132b8cf9 --- /dev/null +++ b/src/supermemory/types/memory_update_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["MemoryUpdateResponse"] + + +class MemoryUpdateResponse(BaseModel): + id: str + + status: str diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 304e16ec..cab6854a 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -14,6 +14,7 @@ MemoryGetResponse, MemoryListResponse, MemoryDeleteResponse, + MemoryUpdateResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,6 +23,75 @@ class TestMemories: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() + @parametrize + def test_method_update(self, client: Supermemory) -> None: + memory = client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_update_with_all_params(self, client: Supermemory) -> None: + memory = client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + container_tags=["string"], + metadata={ + "source": "web", + "category": "technology", + "tag_1": "ai", + "tag_2": "machine-learning", + "readingTime": 5, + "isPublic": True, + }, + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_update(self, client: Supermemory) -> None: + response = client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_update(self, client: Supermemory) -> None: + with client.memories.with_streaming_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_update(self, client: Supermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_id` but received ''"): + client.memories.with_raw_response.update( + path_id="", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) + @pytest.mark.skip() @parametrize def test_method_list(self, client: Supermemory) -> None: @@ -203,6 +273,75 @@ def test_path_params_get(self, client: Supermemory) -> None: class TestAsyncMemories: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() + @parametrize + async def test_method_update(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + container_tags=["string"], + metadata={ + "source": "web", + "category": "technology", + "tag_1": "ai", + "tag_2": "machine-learning", + "readingTime": 5, + "isPublic": True, + }, + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_update(self, async_client: AsyncSupermemory) -> None: + response = await async_client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_update(self, async_client: AsyncSupermemory) -> None: + async with async_client.memories.with_streaming_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_update(self, async_client: AsyncSupermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_id` but received ''"): + await async_client.memories.with_raw_response.update( + path_id="", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncSupermemory) -> None: diff --git a/tests/test_client.py b/tests/test_client.py index 977e1f61..678f7465 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,6 +23,7 @@ from supermemory import Supermemory, AsyncSupermemory, APIResponseValidationError from supermemory._types import Omit +from supermemory._utils import maybe_transform from supermemory._models import BaseModel, FinalRequestOptions from supermemory._constants import RAW_RESPONSE_HEADER from supermemory._exceptions import APIStatusError, APITimeoutError, SupermemoryError, APIResponseValidationError @@ -32,6 +33,7 @@ BaseClient, make_request_options, ) +from supermemory.types.memory_update_params import MemoryUpdateParams from .utils import update_env @@ -722,11 +724,23 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.delete("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.patch("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.delete( - "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} + self.client.patch( + "/v3/memories/id", + body=cast( + object, + maybe_transform( + dict( + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ), + MemoryUpdateParams, + ), + ), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -734,11 +748,23 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.delete("/v3/memories/id").mock(return_value=httpx.Response(500)) + respx_mock.patch("/v3/memories/id").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.delete( - "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} + self.client.patch( + "/v3/memories/id", + body=cast( + object, + maybe_transform( + dict( + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ), + MemoryUpdateParams, + ), + ), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -767,9 +793,13 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.list("id") + response = client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -791,9 +821,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": Omit()}) + response = client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + extra_headers={"x-stainless-retry-count": Omit()}, + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -814,9 +849,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": "42"}) + response = client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + extra_headers={"x-stainless-retry-count": "42"}, + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1492,11 +1532,23 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.delete("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.patch("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.delete( - "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} + await self.client.patch( + "/v3/memories/id", + body=cast( + object, + maybe_transform( + dict( + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ), + MemoryUpdateParams, + ), + ), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -1504,11 +1556,23 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.delete("/v3/memories/id").mock(return_value=httpx.Response(500)) + respx_mock.patch("/v3/memories/id").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.delete( - "/v3/memories/id", cast_to=httpx.Response, options={"headers": {RAW_RESPONSE_HEADER: "stream"}} + await self.client.patch( + "/v3/memories/id", + body=cast( + object, + maybe_transform( + dict( + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ), + MemoryUpdateParams, + ), + ), + cast_to=httpx.Response, + options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, ) assert _get_open_connections(self.client) == 0 @@ -1538,9 +1602,13 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.list("id") + response = await client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + ) assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1563,9 +1631,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": Omit()}) + response = await client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + extra_headers={"x-stainless-retry-count": Omit()}, + ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1587,9 +1660,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.delete("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.list("id", extra_headers={"x-stainless-retry-count": "42"}) + response = await client.memories.with_raw_response.update( + path_id="id", + body_id="acxV5LHMEsG2hMSNb4umbn", + content="This is a detailed article about machine learning concepts...", + extra_headers={"x-stainless-retry-count": "42"}, + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From 5b0c81086db77a2ea5922d117f4e393475d2bd03 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 14:50:23 +0000 Subject: [PATCH 12/24] feat(api): manual updates --- .stats.yml | 4 +- README.md | 38 +-- api.md | 12 + src/supermemory/_client.py | 10 +- src/supermemory/resources/__init__.py | 14 + src/supermemory/resources/search.py | 296 ++++++++++++++++++ src/supermemory/types/__init__.py | 2 + .../types/search_execute_params.py | 86 +++++ .../types/search_execute_response.py | 52 +++ tests/api_resources/test_search.py | 160 ++++++++++ tests/test_client.py | 90 ++---- 11 files changed, 676 insertions(+), 88 deletions(-) create mode 100644 src/supermemory/resources/search.py create mode 100644 src/supermemory/types/search_execute_params.py create mode 100644 src/supermemory/types/search_execute_response.py create mode 100644 tests/api_resources/test_search.py diff --git a/.stats.yml b/.stats.yml index 3ae21f36..3b889ac2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 9 +configured_endpoints: 10 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-a280bf0a406a709c156d5e3fe7c8ae93ebe607d937af8d725d0a2997cab07a7d.yml openapi_spec_hash: 15c602571db9cbcce4703c929b994d82 -config_hash: b9c958a39a94966479e516e9061818be +config_hash: 1c771b2d30afc18bf405a425ea1c327a diff --git a/README.md b/README.md index 8bdb29a8..43bb97a0 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,10 @@ client = Supermemory( api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted ) -memory = client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", +response = client.search.execute( + q="documents related to python", ) -print(memory.id) +print(response.results) ``` While you can provide an `api_key` keyword argument, @@ -59,12 +57,10 @@ client = AsyncSupermemory( async def main() -> None: - memory = await client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", + response = await client.search.execute( + q="documents related to python", ) - print(memory.id) + print(response.results) asyncio.run(main()) @@ -97,9 +93,7 @@ from supermemory import Supermemory client = Supermemory() try: - client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + client.memories.add( content="This is a detailed article about machine learning concepts...", ) except supermemory.APIConnectionError as e: @@ -144,9 +138,7 @@ client = Supermemory( ) # Or, configure per-request: -client.with_options(max_retries=5).memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", +client.with_options(max_retries=5).memories.add( content="This is a detailed article about machine learning concepts...", ) ``` @@ -171,9 +163,7 @@ client = Supermemory( ) # Override per-request: -client.with_options(timeout=5.0).memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", +client.with_options(timeout=5.0).memories.add( content="This is a detailed article about machine learning concepts...", ) ``` @@ -216,14 +206,12 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from supermemory import Supermemory client = Supermemory() -response = client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", +response = client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", ) print(response.headers.get('X-My-Header')) -memory = response.parse() # get the object that `memories.update()` would have returned +memory = response.parse() # get the object that `memories.add()` would have returned print(memory.id) ``` @@ -238,9 +226,7 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.memories.with_streaming_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", +with client.memories.with_streaming_response.add( content="This is a detailed article about machine learning concepts...", ) as response: print(response.headers.get("X-My-Header")) diff --git a/api.md b/api.md index bbc056bf..fd254042 100644 --- a/api.md +++ b/api.md @@ -20,6 +20,18 @@ Methods: - client.memories.add(\*\*params) -> MemoryAddResponse - client.memories.get(id) -> MemoryGetResponse +# Search + +Types: + +```python +from supermemory.types import SearchExecuteResponse +``` + +Methods: + +- client.search.execute(\*\*params) -> SearchExecuteResponse + # Settings Types: diff --git a/src/supermemory/_client.py b/src/supermemory/_client.py index 0a336bf6..e2e99f0b 100644 --- a/src/supermemory/_client.py +++ b/src/supermemory/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import memories, settings, connections +from .resources import search, memories, settings, connections from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, SupermemoryError from ._base_client import ( @@ -44,6 +44,7 @@ class Supermemory(SyncAPIClient): memories: memories.MemoriesResource + search: search.SearchResource settings: settings.SettingsResource connections: connections.ConnectionsResource with_raw_response: SupermemoryWithRawResponse @@ -104,6 +105,7 @@ def __init__( ) self.memories = memories.MemoriesResource(self) + self.search = search.SearchResource(self) self.settings = settings.SettingsResource(self) self.connections = connections.ConnectionsResource(self) self.with_raw_response = SupermemoryWithRawResponse(self) @@ -216,6 +218,7 @@ def _make_status_error( class AsyncSupermemory(AsyncAPIClient): memories: memories.AsyncMemoriesResource + search: search.AsyncSearchResource settings: settings.AsyncSettingsResource connections: connections.AsyncConnectionsResource with_raw_response: AsyncSupermemoryWithRawResponse @@ -276,6 +279,7 @@ def __init__( ) self.memories = memories.AsyncMemoriesResource(self) + self.search = search.AsyncSearchResource(self) self.settings = settings.AsyncSettingsResource(self) self.connections = connections.AsyncConnectionsResource(self) self.with_raw_response = AsyncSupermemoryWithRawResponse(self) @@ -389,6 +393,7 @@ def _make_status_error( class SupermemoryWithRawResponse: def __init__(self, client: Supermemory) -> None: self.memories = memories.MemoriesResourceWithRawResponse(client.memories) + self.search = search.SearchResourceWithRawResponse(client.search) self.settings = settings.SettingsResourceWithRawResponse(client.settings) self.connections = connections.ConnectionsResourceWithRawResponse(client.connections) @@ -396,6 +401,7 @@ def __init__(self, client: Supermemory) -> None: class AsyncSupermemoryWithRawResponse: def __init__(self, client: AsyncSupermemory) -> None: self.memories = memories.AsyncMemoriesResourceWithRawResponse(client.memories) + self.search = search.AsyncSearchResourceWithRawResponse(client.search) self.settings = settings.AsyncSettingsResourceWithRawResponse(client.settings) self.connections = connections.AsyncConnectionsResourceWithRawResponse(client.connections) @@ -403,6 +409,7 @@ def __init__(self, client: AsyncSupermemory) -> None: class SupermemoryWithStreamedResponse: def __init__(self, client: Supermemory) -> None: self.memories = memories.MemoriesResourceWithStreamingResponse(client.memories) + self.search = search.SearchResourceWithStreamingResponse(client.search) self.settings = settings.SettingsResourceWithStreamingResponse(client.settings) self.connections = connections.ConnectionsResourceWithStreamingResponse(client.connections) @@ -410,6 +417,7 @@ def __init__(self, client: Supermemory) -> None: class AsyncSupermemoryWithStreamedResponse: def __init__(self, client: AsyncSupermemory) -> None: self.memories = memories.AsyncMemoriesResourceWithStreamingResponse(client.memories) + self.search = search.AsyncSearchResourceWithStreamingResponse(client.search) self.settings = settings.AsyncSettingsResourceWithStreamingResponse(client.settings) self.connections = connections.AsyncConnectionsResourceWithStreamingResponse(client.connections) diff --git a/src/supermemory/resources/__init__.py b/src/supermemory/resources/__init__.py index 5a1fb723..275ecfbe 100644 --- a/src/supermemory/resources/__init__.py +++ b/src/supermemory/resources/__init__.py @@ -1,5 +1,13 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from .search import ( + SearchResource, + AsyncSearchResource, + SearchResourceWithRawResponse, + AsyncSearchResourceWithRawResponse, + SearchResourceWithStreamingResponse, + AsyncSearchResourceWithStreamingResponse, +) from .memories import ( MemoriesResource, AsyncMemoriesResource, @@ -32,6 +40,12 @@ "AsyncMemoriesResourceWithRawResponse", "MemoriesResourceWithStreamingResponse", "AsyncMemoriesResourceWithStreamingResponse", + "SearchResource", + "AsyncSearchResource", + "SearchResourceWithRawResponse", + "AsyncSearchResourceWithRawResponse", + "SearchResourceWithStreamingResponse", + "AsyncSearchResourceWithStreamingResponse", "SettingsResource", "AsyncSettingsResource", "SettingsResourceWithRawResponse", diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py new file mode 100644 index 00000000..4e293996 --- /dev/null +++ b/src/supermemory/resources/search.py @@ -0,0 +1,296 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List +from typing_extensions import Literal + +import httpx + +from ..types import search_execute_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._utils import maybe_transform, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from .._base_client import make_request_options +from ..types.search_execute_response import SearchExecuteResponse + +__all__ = ["SearchResource", "AsyncSearchResource"] + + +class SearchResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> SearchResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers + """ + return SearchResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> SearchResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response + """ + return SearchResourceWithStreamingResponse(self) + + def execute( + self, + *, + q: str, + categories_filter: List[Literal["technology", "science", "business", "health"]] | NotGiven = NOT_GIVEN, + chunk_threshold: float | NotGiven = NOT_GIVEN, + doc_id: str | NotGiven = NOT_GIVEN, + document_threshold: float | NotGiven = NOT_GIVEN, + filters: search_execute_params.Filters | NotGiven = NOT_GIVEN, + include_summary: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + only_matching_chunks: bool | NotGiven = NOT_GIVEN, + rerank: bool | NotGiven = NOT_GIVEN, + rewrite_query: bool | NotGiven = NOT_GIVEN, + user_id: str | NotGiven = NOT_GIVEN, + # 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, + ) -> SearchExecuteResponse: + """ + Search memories with filtering + + Args: + q: Search query string + + categories_filter: Optional category filters + + chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most + chunks, more results), 1 is most sensitive (returns lesser chunks, accurate + results) + + doc_id: Optional document ID to search within. You can use this to find chunks in a very + large document. + + document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns + most documents, more results), 1 is most sensitive (returns lesser documents, + accurate results) + + filters: Optional filters to apply to the search + + include_summary: If true, include document summary in the response. This is helpful if you want a + chatbot to know the full context of the document. + + limit: Maximum number of results to return + + only_matching_chunks: If true, only return matching chunks without context. Normally, we send the + previous and next chunk to provide more context for LLMs. If you only want the + matching chunk, set this to true. + + rerank: If true, rerank the results based on the query. This is helpful if you want to + ensure the most relevant results are returned. + + rewrite_query: If true, rewrites the query to make it easier to find documents. This increases + the latency by about 400ms + + user_id: End user ID this search is associated with. NOTE: This also acts as a filter for + the search. + + 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 + """ + return self._get( + "/v3/search", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "q": q, + "categories_filter": categories_filter, + "chunk_threshold": chunk_threshold, + "doc_id": doc_id, + "document_threshold": document_threshold, + "filters": filters, + "include_summary": include_summary, + "limit": limit, + "only_matching_chunks": only_matching_chunks, + "rerank": rerank, + "rewrite_query": rewrite_query, + "user_id": user_id, + }, + search_execute_params.SearchExecuteParams, + ), + ), + cast_to=SearchExecuteResponse, + ) + + +class AsyncSearchResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncSearchResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncSearchResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncSearchResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response + """ + return AsyncSearchResourceWithStreamingResponse(self) + + async def execute( + self, + *, + q: str, + categories_filter: List[Literal["technology", "science", "business", "health"]] | NotGiven = NOT_GIVEN, + chunk_threshold: float | NotGiven = NOT_GIVEN, + doc_id: str | NotGiven = NOT_GIVEN, + document_threshold: float | NotGiven = NOT_GIVEN, + filters: search_execute_params.Filters | NotGiven = NOT_GIVEN, + include_summary: bool | NotGiven = NOT_GIVEN, + limit: int | NotGiven = NOT_GIVEN, + only_matching_chunks: bool | NotGiven = NOT_GIVEN, + rerank: bool | NotGiven = NOT_GIVEN, + rewrite_query: bool | NotGiven = NOT_GIVEN, + user_id: str | NotGiven = NOT_GIVEN, + # 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, + ) -> SearchExecuteResponse: + """ + Search memories with filtering + + Args: + q: Search query string + + categories_filter: Optional category filters + + chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most + chunks, more results), 1 is most sensitive (returns lesser chunks, accurate + results) + + doc_id: Optional document ID to search within. You can use this to find chunks in a very + large document. + + document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns + most documents, more results), 1 is most sensitive (returns lesser documents, + accurate results) + + filters: Optional filters to apply to the search + + include_summary: If true, include document summary in the response. This is helpful if you want a + chatbot to know the full context of the document. + + limit: Maximum number of results to return + + only_matching_chunks: If true, only return matching chunks without context. Normally, we send the + previous and next chunk to provide more context for LLMs. If you only want the + matching chunk, set this to true. + + rerank: If true, rerank the results based on the query. This is helpful if you want to + ensure the most relevant results are returned. + + rewrite_query: If true, rewrites the query to make it easier to find documents. This increases + the latency by about 400ms + + user_id: End user ID this search is associated with. NOTE: This also acts as a filter for + the search. + + 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 + """ + return await self._get( + "/v3/search", + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "q": q, + "categories_filter": categories_filter, + "chunk_threshold": chunk_threshold, + "doc_id": doc_id, + "document_threshold": document_threshold, + "filters": filters, + "include_summary": include_summary, + "limit": limit, + "only_matching_chunks": only_matching_chunks, + "rerank": rerank, + "rewrite_query": rewrite_query, + "user_id": user_id, + }, + search_execute_params.SearchExecuteParams, + ), + ), + cast_to=SearchExecuteResponse, + ) + + +class SearchResourceWithRawResponse: + def __init__(self, search: SearchResource) -> None: + self._search = search + + self.execute = to_raw_response_wrapper( + search.execute, + ) + + +class AsyncSearchResourceWithRawResponse: + def __init__(self, search: AsyncSearchResource) -> None: + self._search = search + + self.execute = async_to_raw_response_wrapper( + search.execute, + ) + + +class SearchResourceWithStreamingResponse: + def __init__(self, search: SearchResource) -> None: + self._search = search + + self.execute = to_streamed_response_wrapper( + search.execute, + ) + + +class AsyncSearchResourceWithStreamingResponse: + def __init__(self, search: AsyncSearchResource) -> None: + self._search = search + + self.execute = async_to_streamed_response_wrapper( + search.execute, + ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 1dc2977e..9cee290e 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -8,10 +8,12 @@ from .memory_list_response import MemoryListResponse as MemoryListResponse from .memory_update_params import MemoryUpdateParams as MemoryUpdateParams from .setting_get_response import SettingGetResponse as SettingGetResponse +from .search_execute_params import SearchExecuteParams as SearchExecuteParams from .setting_update_params import SettingUpdateParams as SettingUpdateParams from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse from .memory_update_response import MemoryUpdateResponse as MemoryUpdateResponse from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse +from .search_execute_response import SearchExecuteResponse as SearchExecuteResponse from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse from .connection_list_response import ConnectionListResponse as ConnectionListResponse from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse diff --git a/src/supermemory/types/search_execute_params.py b/src/supermemory/types/search_execute_params.py new file mode 100644 index 00000000..bc3b4756 --- /dev/null +++ b/src/supermemory/types/search_execute_params.py @@ -0,0 +1,86 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union, Iterable +from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["SearchExecuteParams", "Filters", "FiltersUnionMember0"] + + +class SearchExecuteParams(TypedDict, total=False): + q: Required[str] + """Search query string""" + + categories_filter: Annotated[ + List[Literal["technology", "science", "business", "health"]], PropertyInfo(alias="categoriesFilter") + ] + """Optional category filters""" + + chunk_threshold: Annotated[float, PropertyInfo(alias="chunkThreshold")] + """Threshold / sensitivity for chunk selection. + + 0 is least sensitive (returns most chunks, more results), 1 is most sensitive + (returns lesser chunks, accurate results) + """ + + doc_id: Annotated[str, PropertyInfo(alias="docId")] + """Optional document ID to search within. + + You can use this to find chunks in a very large document. + """ + + document_threshold: Annotated[float, PropertyInfo(alias="documentThreshold")] + """Threshold / sensitivity for document selection. + + 0 is least sensitive (returns most documents, more results), 1 is most sensitive + (returns lesser documents, accurate results) + """ + + filters: Filters + """Optional filters to apply to the search""" + + include_summary: Annotated[bool, PropertyInfo(alias="includeSummary")] + """If true, include document summary in the response. + + This is helpful if you want a chatbot to know the full context of the document. + """ + + limit: int + """Maximum number of results to return""" + + only_matching_chunks: Annotated[bool, PropertyInfo(alias="onlyMatchingChunks")] + """If true, only return matching chunks without context. + + Normally, we send the previous and next chunk to provide more context for LLMs. + If you only want the matching chunk, set this to true. + """ + + rerank: bool + """If true, rerank the results based on the query. + + This is helpful if you want to ensure the most relevant results are returned. + """ + + rewrite_query: Annotated[bool, PropertyInfo(alias="rewriteQuery")] + """If true, rewrites the query to make it easier to find documents. + + This increases the latency by about 400ms + """ + + user_id: Annotated[str, PropertyInfo(alias="userId")] + """End user ID this search is associated with. + + NOTE: This also acts as a filter for the search. + """ + + +class FiltersUnionMember0(TypedDict, total=False): + and_: Annotated[Iterable[object], PropertyInfo(alias="AND")] + + or_: Annotated[Iterable[object], PropertyInfo(alias="OR")] + + +Filters: TypeAlias = Union[FiltersUnionMember0, Dict[str, object]] diff --git a/src/supermemory/types/search_execute_response.py b/src/supermemory/types/search_execute_response.py new file mode 100644 index 00000000..5b2bc4e0 --- /dev/null +++ b/src/supermemory/types/search_execute_response.py @@ -0,0 +1,52 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional +from datetime import datetime + +from pydantic import Field as FieldInfo + +from .._models import BaseModel + +__all__ = ["SearchExecuteResponse", "Result", "ResultChunk"] + + +class ResultChunk(BaseModel): + content: str + """Content of the matching chunk""" + + is_relevant: bool = FieldInfo(alias="isRelevant") + """Whether this chunk is relevant to the query""" + + score: float + """Similarity score for this chunk""" + + +class Result(BaseModel): + chunks: List[ResultChunk] + """Matching content chunks from the document""" + + created_at: datetime = FieldInfo(alias="createdAt") + """Document creation date""" + + document_id: str = FieldInfo(alias="documentId") + """ID of the matching document""" + + metadata: Optional[Dict[str, object]] = None + """Document metadata""" + + score: float + """Relevance score of the match""" + + title: str + """Document title""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + """Document last update date""" + + +class SearchExecuteResponse(BaseModel): + results: List[Result] + + timing: float + + total: float diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py new file mode 100644 index 00000000..c0557ff5 --- /dev/null +++ b/tests/api_resources/test_search.py @@ -0,0 +1,160 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import os +from typing import Any, cast + +import pytest + +from supermemory import Supermemory, AsyncSupermemory +from tests.utils import assert_matches_type +from supermemory.types import SearchExecuteResponse + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestSearch: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + def test_method_execute(self, client: Supermemory) -> None: + search = client.search.execute( + q="machine learning concepts", + ) + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_execute_with_all_params(self, client: Supermemory) -> None: + search = client.search.execute( + q="machine learning concepts", + categories_filter=["technology", "science"], + chunk_threshold=0.5, + doc_id="doc_xyz789", + document_threshold=0.5, + filters={ + "and_": [ + { + "key": "group", + "value": "jira_users", + "negate": False, + }, + { + "filterType": "numeric", + "key": "timestamp", + "value": "1742745777", + "negate": False, + "numericOperator": ">", + }, + ], + "or_": [{}], + }, + include_summary=False, + limit=10, + only_matching_chunks=False, + rerank=False, + rewrite_query=False, + user_id="user_123", + ) + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_execute(self, client: Supermemory) -> None: + response = client.search.with_raw_response.execute( + q="machine learning concepts", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + search = response.parse() + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_execute(self, client: Supermemory) -> None: + with client.search.with_streaming_response.execute( + q="machine learning concepts", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + search = response.parse() + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncSearch: + parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + + @pytest.mark.skip() + @parametrize + async def test_method_execute(self, async_client: AsyncSupermemory) -> None: + search = await async_client.search.execute( + q="machine learning concepts", + ) + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_execute_with_all_params(self, async_client: AsyncSupermemory) -> None: + search = await async_client.search.execute( + q="machine learning concepts", + categories_filter=["technology", "science"], + chunk_threshold=0.5, + doc_id="doc_xyz789", + document_threshold=0.5, + filters={ + "and_": [ + { + "key": "group", + "value": "jira_users", + "negate": False, + }, + { + "filterType": "numeric", + "key": "timestamp", + "value": "1742745777", + "negate": False, + "numericOperator": ">", + }, + ], + "or_": [{}], + }, + include_summary=False, + limit=10, + only_matching_chunks=False, + rerank=False, + rewrite_query=False, + user_id="user_123", + ) + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_execute(self, async_client: AsyncSupermemory) -> None: + response = await async_client.search.with_raw_response.execute( + q="machine learning concepts", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + search = await response.parse() + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_execute(self, async_client: AsyncSupermemory) -> None: + async with async_client.search.with_streaming_response.execute( + q="machine learning concepts", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + search = await response.parse() + assert_matches_type(SearchExecuteResponse, search, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index 678f7465..3636a001 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -33,7 +33,7 @@ BaseClient, make_request_options, ) -from supermemory.types.memory_update_params import MemoryUpdateParams +from supermemory.types.memory_add_params import MemoryAddParams from .utils import update_env @@ -724,19 +724,15 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.patch("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v3/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.patch( - "/v3/memories/id", + self.client.post( + "/v3/memories", body=cast( object, maybe_transform( - dict( - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", - ), - MemoryUpdateParams, + dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -748,19 +744,15 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> No @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.patch("/v3/memories/id").mock(return_value=httpx.Response(500)) + respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.patch( - "/v3/memories/id", + self.client.post( + "/v3/memories", body=cast( object, maybe_transform( - dict( - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", - ), - MemoryUpdateParams, + dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -793,12 +785,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", + response = client.memories.with_raw_response.add( + content="This is a detailed article about machine learning concepts..." ) assert response.retries_taken == failures_before_success @@ -821,11 +811,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + response = client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -849,11 +837,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + response = client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", extra_headers={"x-stainless-retry-count": "42"}, ) @@ -1532,19 +1518,15 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.patch("/v3/memories/id").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/v3/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.patch( - "/v3/memories/id", + await self.client.post( + "/v3/memories", body=cast( object, maybe_transform( - dict( - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", - ), - MemoryUpdateParams, + dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -1556,19 +1538,15 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter) @mock.patch("supermemory._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter) -> None: - respx_mock.patch("/v3/memories/id").mock(return_value=httpx.Response(500)) + respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.patch( - "/v3/memories/id", + await self.client.post( + "/v3/memories", body=cast( object, maybe_transform( - dict( - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", - ), - MemoryUpdateParams, + dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -1602,12 +1580,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", - content="This is a detailed article about machine learning concepts...", + response = await client.memories.with_raw_response.add( + content="This is a detailed article about machine learning concepts..." ) assert response.retries_taken == failures_before_success @@ -1631,11 +1607,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + response = await client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -1660,11 +1634,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.patch("/v3/memories/id").mock(side_effect=retry_handler) + respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + response = await client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", extra_headers={"x-stainless-retry-count": "42"}, ) From 4a6b77aa6cd55d7135e33cbfb1138d9b2d00d40a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 14:53:35 +0000 Subject: [PATCH 13/24] feat(api): manual updates --- .stats.yml | 8 +- api.md | 4 +- src/supermemory/resources/memories.py | 89 ++++++++++++++---- src/supermemory/types/__init__.py | 1 + .../types/memory_delete_response.py | 4 +- src/supermemory/types/memory_list_params.py | 24 +++++ src/supermemory/types/memory_list_response.py | 90 ++++++++++++++++++- tests/api_resources/test_memories.py | 56 ++++++------ 8 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 src/supermemory/types/memory_list_params.py diff --git a/.stats.yml b/.stats.yml index 3b889ac2..45b7a90b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 10 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-a280bf0a406a709c156d5e3fe7c8ae93ebe607d937af8d725d0a2997cab07a7d.yml -openapi_spec_hash: 15c602571db9cbcce4703c929b994d82 -config_hash: 1c771b2d30afc18bf405a425ea1c327a +configured_endpoints: 11 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-9f6d4050800c7cde90cbc3d1b59c782955e3cc1f73790c13a1924a000e09ec14.yml +openapi_spec_hash: 92739eccad02248cc2bf07874e299fec +config_hash: 3a000ea344f2f52c50cf06452fcf52ca diff --git a/api.md b/api.md index fd254042..0c367901 100644 --- a/api.md +++ b/api.md @@ -15,8 +15,8 @@ from supermemory.types import ( Methods: - client.memories.update(path_id, \*\*params) -> MemoryUpdateResponse -- client.memories.list(id) -> MemoryListResponse -- client.memories.delete(id) -> MemoryDeleteResponse +- client.memories.list(\*\*params) -> MemoryListResponse +- client.memories.delete(id) -> MemoryDeleteResponse - client.memories.add(\*\*params) -> MemoryAddResponse - client.memories.get(id) -> MemoryGetResponse diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index 59d5fbc1..5c050e6a 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -3,10 +3,11 @@ from __future__ import annotations from typing import Dict, List, Union +from typing_extensions import Literal import httpx -from ..types import memory_add_params, memory_update_params +from ..types import memory_add_params, memory_list_params, memory_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -95,8 +96,12 @@ def update( def list( self, - id: str, *, + filters: str | NotGiven = NOT_GIVEN, + limit: str | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + page: str | NotGiven = NOT_GIVEN, + sort: Literal["createdAt", "updatedAt"] | NotGiven = NOT_GIVEN, # 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, @@ -105,9 +110,19 @@ def list( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MemoryListResponse: """ - Delete a memory + Retrieves a paginated list of memories with their metadata and workflow status Args: + filters: Optional filters to apply to the search + + limit: Number of items per page + + order: Sort order + + page: Page number to fetch + + sort: Field to sort by + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -116,12 +131,23 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._delete( - f"/v3/memories/{id}", + return self._get( + "/v3/memories", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "filters": filters, + "limit": limit, + "order": order, + "page": page, + "sort": sort, + }, + memory_list_params.MemoryListParams, + ), ), cast_to=MemoryListResponse, ) @@ -138,7 +164,7 @@ def delete( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MemoryDeleteResponse: """ - Get a memory by ID + Delete a memory Args: extra_headers: Send extra headers @@ -151,7 +177,7 @@ def delete( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._get( + return self._delete( f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -302,8 +328,12 @@ async def update( async def list( self, - id: str, *, + filters: str | NotGiven = NOT_GIVEN, + limit: str | NotGiven = NOT_GIVEN, + order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, + page: str | NotGiven = NOT_GIVEN, + sort: Literal["createdAt", "updatedAt"] | NotGiven = NOT_GIVEN, # 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, @@ -312,9 +342,19 @@ async def list( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MemoryListResponse: """ - Delete a memory + Retrieves a paginated list of memories with their metadata and workflow status Args: + filters: Optional filters to apply to the search + + limit: Number of items per page + + order: Sort order + + page: Page number to fetch + + sort: Field to sort by + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -323,12 +363,23 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ - if not id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._delete( - f"/v3/memories/{id}", + return await self._get( + "/v3/memories", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "filters": filters, + "limit": limit, + "order": order, + "page": page, + "sort": sort, + }, + memory_list_params.MemoryListParams, + ), ), cast_to=MemoryListResponse, ) @@ -345,7 +396,7 @@ async def delete( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> MemoryDeleteResponse: """ - Get a memory by ID + Delete a memory Args: extra_headers: Send extra headers @@ -358,7 +409,7 @@ async def delete( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._get( + return await self._delete( f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 9cee290e..2545ab27 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations from .memory_add_params import MemoryAddParams as MemoryAddParams +from .memory_list_params import MemoryListParams as MemoryListParams from .memory_add_response import MemoryAddResponse as MemoryAddResponse from .memory_get_response import MemoryGetResponse as MemoryGetResponse from .memory_list_response import MemoryListResponse as MemoryListResponse diff --git a/src/supermemory/types/memory_delete_response.py b/src/supermemory/types/memory_delete_response.py index fe31c22e..0f17a7e1 100644 --- a/src/supermemory/types/memory_delete_response.py +++ b/src/supermemory/types/memory_delete_response.py @@ -6,6 +6,4 @@ class MemoryDeleteResponse(BaseModel): - id: str - - status: str + success: bool diff --git a/src/supermemory/types/memory_list_params.py b/src/supermemory/types/memory_list_params.py new file mode 100644 index 00000000..d40690da --- /dev/null +++ b/src/supermemory/types/memory_list_params.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Literal, TypedDict + +__all__ = ["MemoryListParams"] + + +class MemoryListParams(TypedDict, total=False): + filters: str + """Optional filters to apply to the search""" + + limit: str + """Number of items per page""" + + order: Literal["asc", "desc"] + """Sort order""" + + page: str + """Page number to fetch""" + + sort: Literal["createdAt", "updatedAt"] + """Field to sort by""" diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index 0fecfe23..5b325c78 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -1,9 +1,95 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + from .._models import BaseModel -__all__ = ["MemoryListResponse"] +__all__ = ["MemoryListResponse", "Memory", "Pagination"] + + +class Memory(BaseModel): + id: str + """Unique identifier of the memory.""" + + content: Optional[str] = None + """The content to extract and process into a memory. + + This can be a URL to a website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + """ + + created_at: datetime = FieldInfo(alias="createdAt") + """Creation timestamp""" + + custom_id: Optional[str] = FieldInfo(alias="customId", default=None) + """Optional custom ID of the memory. + + This could be an ID from your database that will uniquely identify this memory. + """ + + metadata: Union[str, float, bool, Dict[str, object], List[object], None] = None + """Optional metadata for the memory. + + This is used to store additional information about the memory. You can use this + to store any additional information you need about the memory. Metadata can be + filtered through. Keys must be strings and are case sensitive. Values can be + strings, numbers, or booleans. You cannot nest objects. + """ + + og_image: Optional[str] = FieldInfo(alias="ogImage", default=None) + + source: Optional[str] = None + + status: Literal["unknown", "queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"] + """Status of the memory""" + + summary: Optional[str] = None + """Summary of the memory content""" + + title: Optional[str] = None + """Title of the memory""" + + type: Literal["text", "pdf", "tweet", "google_doc", "image", "video", "notion_doc", "webpage"] + """Type of the memory""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + """Last update timestamp""" + + url: Optional[str] = None + """URL of the memory""" + + container_tags: Optional[List[str]] = FieldInfo(alias="containerTags", default=None) + """Optional tags this memory should be containerized by. + + This can be an ID for your user, a project ID, or any other identifier you wish + to use to group memories. + """ + + raw: None = None + """Raw content of the memory""" + + +class Pagination(BaseModel): + current_page: float = FieldInfo(alias="currentPage") + + limit: float + + total_items: float = FieldInfo(alias="totalItems") + + total_pages: float = FieldInfo(alias="totalPages") class MemoryListResponse(BaseModel): - success: bool + memories: List[Memory] + + pagination: Pagination + """Pagination metadata""" diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index cab6854a..228dcf39 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -95,17 +95,25 @@ def test_path_params_update(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_method_list(self, client: Supermemory) -> None: + memory = client.memories.list() + assert_matches_type(MemoryListResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Supermemory) -> None: memory = client.memories.list( - "id", + filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', + limit="10", + order="desc", + page="1", + sort="createdAt", ) assert_matches_type(MemoryListResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Supermemory) -> None: - response = client.memories.with_raw_response.list( - "id", - ) + response = client.memories.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -115,9 +123,7 @@ def test_raw_response_list(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_list(self, client: Supermemory) -> None: - with client.memories.with_streaming_response.list( - "id", - ) as response: + with client.memories.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -126,14 +132,6 @@ def test_streaming_response_list(self, client: Supermemory) -> None: assert cast(Any, response.is_closed) is True - @pytest.mark.skip() - @parametrize - def test_path_params_list(self, client: Supermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.memories.with_raw_response.list( - "", - ) - @pytest.mark.skip() @parametrize def test_method_delete(self, client: Supermemory) -> None: @@ -345,17 +343,25 @@ async def test_path_params_update(self, async_client: AsyncSupermemory) -> None: @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.list() + assert_matches_type(MemoryListResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncSupermemory) -> None: memory = await async_client.memories.list( - "id", + filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', + limit="10", + order="desc", + page="1", + sort="createdAt", ) assert_matches_type(MemoryListResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memories.with_raw_response.list( - "id", - ) + response = await async_client.memories.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -365,9 +371,7 @@ async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: @pytest.mark.skip() @parametrize async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> None: - async with async_client.memories.with_streaming_response.list( - "id", - ) as response: + async with async_client.memories.with_streaming_response.list() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -376,14 +380,6 @@ async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> assert cast(Any, response.is_closed) is True - @pytest.mark.skip() - @parametrize - async def test_path_params_list(self, async_client: AsyncSupermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.memories.with_raw_response.list( - "", - ) - @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncSupermemory) -> None: From 3e6314dba381eb65fe644941f2cca25dfcd93d3d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 14:55:19 +0000 Subject: [PATCH 14/24] feat(api): manual updates --- .stats.yml | 4 +-- api.md | 2 +- src/supermemory/resources/memories.py | 20 +++++------ src/supermemory/types/memory_update_params.py | 2 -- tests/api_resources/test_memories.py | 34 +++++++------------ 5 files changed, 23 insertions(+), 39 deletions(-) diff --git a/.stats.yml b/.stats.yml index 45b7a90b..9bf150d2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-9f6d4050800c7cde90cbc3d1b59c782955e3cc1f73790c13a1924a000e09ec14.yml -openapi_spec_hash: 92739eccad02248cc2bf07874e299fec +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-0293efeddbd48f43b5336c0d87e7862d61de0ad5b5da8c3c17f93da5d8a8edca.yml +openapi_spec_hash: e94bf330ad1fa4adeea8533abed19be8 config_hash: 3a000ea344f2f52c50cf06452fcf52ca diff --git a/api.md b/api.md index 0c367901..5e185be0 100644 --- a/api.md +++ b/api.md @@ -14,7 +14,7 @@ from supermemory.types import ( Methods: -- client.memories.update(path_id, \*\*params) -> MemoryUpdateResponse +- client.memories.update(id, \*\*params) -> MemoryUpdateResponse - client.memories.list(\*\*params) -> MemoryListResponse - client.memories.delete(id) -> MemoryDeleteResponse - client.memories.add(\*\*params) -> MemoryAddResponse diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index 5c050e6a..e30e6e57 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -50,9 +50,8 @@ def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: def update( self, - path_id: str, + id: str, *, - body_id: str, content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, @@ -75,13 +74,12 @@ def update( timeout: Override the client-level default timeout for this request, in seconds """ - if not path_id: - raise ValueError(f"Expected a non-empty value for `path_id` but received {path_id!r}") + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._patch( - f"/v3/memories/{path_id}", + f"/v3/memories/{id}", body=maybe_transform( { - "body_id": body_id, "content": content, "container_tags": container_tags, "metadata": metadata, @@ -282,9 +280,8 @@ def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: async def update( self, - path_id: str, + id: str, *, - body_id: str, content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, @@ -307,13 +304,12 @@ async def update( timeout: Override the client-level default timeout for this request, in seconds """ - if not path_id: - raise ValueError(f"Expected a non-empty value for `path_id` but received {path_id!r}") + if not id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._patch( - f"/v3/memories/{path_id}", + f"/v3/memories/{id}", body=await async_maybe_transform( { - "body_id": body_id, "content": content, "container_tags": container_tags, "metadata": metadata, diff --git a/src/supermemory/types/memory_update_params.py b/src/supermemory/types/memory_update_params.py index adceb6e0..2e94bfe3 100644 --- a/src/supermemory/types/memory_update_params.py +++ b/src/supermemory/types/memory_update_params.py @@ -11,8 +11,6 @@ class MemoryUpdateParams(TypedDict, total=False): - body_id: Required[Annotated[str, PropertyInfo(alias="id")]] - content: Required[str] container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 228dcf39..239662c5 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -27,8 +27,7 @@ class TestMemories: @parametrize def test_method_update(self, client: Supermemory) -> None: memory = client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", ) assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) @@ -37,8 +36,7 @@ def test_method_update(self, client: Supermemory) -> None: @parametrize def test_method_update_with_all_params(self, client: Supermemory) -> None: memory = client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", container_tags=["string"], metadata={ @@ -56,8 +54,7 @@ def test_method_update_with_all_params(self, client: Supermemory) -> None: @parametrize def test_raw_response_update(self, client: Supermemory) -> None: response = client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", ) @@ -70,8 +67,7 @@ def test_raw_response_update(self, client: Supermemory) -> None: @parametrize def test_streaming_response_update(self, client: Supermemory) -> None: with client.memories.with_streaming_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", ) as response: assert not response.is_closed @@ -85,10 +81,9 @@ def test_streaming_response_update(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_path_params_update(self, client: Supermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): client.memories.with_raw_response.update( - path_id="", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="", content="This is a detailed article about machine learning concepts...", ) @@ -275,8 +270,7 @@ class TestAsyncMemories: @parametrize async def test_method_update(self, async_client: AsyncSupermemory) -> None: memory = await async_client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", ) assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) @@ -285,8 +279,7 @@ async def test_method_update(self, async_client: AsyncSupermemory) -> None: @parametrize async def test_method_update_with_all_params(self, async_client: AsyncSupermemory) -> None: memory = await async_client.memories.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", container_tags=["string"], metadata={ @@ -304,8 +297,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncSupermemor @parametrize async def test_raw_response_update(self, async_client: AsyncSupermemory) -> None: response = await async_client.memories.with_raw_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", ) @@ -318,8 +310,7 @@ async def test_raw_response_update(self, async_client: AsyncSupermemory) -> None @parametrize async def test_streaming_response_update(self, async_client: AsyncSupermemory) -> None: async with async_client.memories.with_streaming_response.update( - path_id="id", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="id", content="This is a detailed article about machine learning concepts...", ) as response: assert not response.is_closed @@ -333,10 +324,9 @@ async def test_streaming_response_update(self, async_client: AsyncSupermemory) - @pytest.mark.skip() @parametrize async def test_path_params_update(self, async_client: AsyncSupermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `path_id` but received ''"): + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): await async_client.memories.with_raw_response.update( - path_id="", - body_id="acxV5LHMEsG2hMSNb4umbn", + id="", content="This is a detailed article about machine learning concepts...", ) From 820350b366310d13abf48604196592b3e1d5c758 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 15:11:52 +0000 Subject: [PATCH 15/24] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9bf150d2..7cbd8a40 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-0293efeddbd48f43b5336c0d87e7862d61de0ad5b5da8c3c17f93da5d8a8edca.yml openapi_spec_hash: e94bf330ad1fa4adeea8533abed19be8 -config_hash: 3a000ea344f2f52c50cf06452fcf52ca +config_hash: 26d0d4c1d69982549448a1e030e54663 From 7a81e8027fd0c76041672c71345e18724e71c10f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 11 May 2025 15:17:05 +0000 Subject: [PATCH 16/24] feat(api): api update --- .stats.yml | 8 +- api.md | 2 - src/supermemory/resources/memories.py | 103 +------------- src/supermemory/types/__init__.py | 2 - .../types/connection_create_response.py | 6 +- src/supermemory/types/memory_update_params.py | 18 --- .../types/memory_update_response.py | 11 -- tests/api_resources/test_memories.py | 129 ------------------ 8 files changed, 7 insertions(+), 272 deletions(-) delete mode 100644 src/supermemory/types/memory_update_params.py delete mode 100644 src/supermemory/types/memory_update_response.py diff --git a/.stats.yml b/.stats.yml index 7cbd8a40..a0e7cabb 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-0293efeddbd48f43b5336c0d87e7862d61de0ad5b5da8c3c17f93da5d8a8edca.yml -openapi_spec_hash: e94bf330ad1fa4adeea8533abed19be8 -config_hash: 26d0d4c1d69982549448a1e030e54663 +configured_endpoints: 10 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-3fa2583744becce1e91ec5ad18f45d2cf17778def3a8f70537a15b08c746c2fb.yml +openapi_spec_hash: bf3c5827e7ddb8b32435aeb671fe7845 +config_hash: c8c1f2b0d63387d621f0cf066ae3379f diff --git a/api.md b/api.md index 5e185be0..8878df76 100644 --- a/api.md +++ b/api.md @@ -4,7 +4,6 @@ Types: ```python from supermemory.types import ( - MemoryUpdateResponse, MemoryListResponse, MemoryDeleteResponse, MemoryAddResponse, @@ -14,7 +13,6 @@ from supermemory.types import ( Methods: -- client.memories.update(id, \*\*params) -> MemoryUpdateResponse - client.memories.list(\*\*params) -> MemoryListResponse - client.memories.delete(id) -> MemoryDeleteResponse - client.memories.add(\*\*params) -> MemoryAddResponse diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index e30e6e57..517c98ee 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -7,7 +7,7 @@ import httpx -from ..types import memory_add_params, memory_list_params, memory_update_params +from ..types import memory_add_params, memory_list_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -23,7 +23,6 @@ from ..types.memory_get_response import MemoryGetResponse from ..types.memory_list_response import MemoryListResponse from ..types.memory_delete_response import MemoryDeleteResponse -from ..types.memory_update_response import MemoryUpdateResponse __all__ = ["MemoriesResource", "AsyncMemoriesResource"] @@ -48,50 +47,6 @@ def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: """ return MemoriesResourceWithStreamingResponse(self) - def update( - self, - id: str, - *, - content: str, - container_tags: List[str] | NotGiven = NOT_GIVEN, - metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, - # 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, - ) -> MemoryUpdateResponse: - """ - Update a memory with any content type (text, url, file, etc.) and metadata - - 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 id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return self._patch( - f"/v3/memories/{id}", - body=maybe_transform( - { - "content": content, - "container_tags": container_tags, - "metadata": metadata, - }, - memory_update_params.MemoryUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=MemoryUpdateResponse, - ) - def list( self, *, @@ -278,50 +233,6 @@ def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: """ return AsyncMemoriesResourceWithStreamingResponse(self) - async def update( - self, - id: str, - *, - content: str, - container_tags: List[str] | NotGiven = NOT_GIVEN, - metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, - # 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, - ) -> MemoryUpdateResponse: - """ - Update a memory with any content type (text, url, file, etc.) and metadata - - 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 id: - raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") - return await self._patch( - f"/v3/memories/{id}", - body=await async_maybe_transform( - { - "content": content, - "container_tags": container_tags, - "metadata": metadata, - }, - memory_update_params.MemoryUpdateParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=MemoryUpdateResponse, - ) - async def list( self, *, @@ -492,9 +403,6 @@ class MemoriesResourceWithRawResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories - self.update = to_raw_response_wrapper( - memories.update, - ) self.list = to_raw_response_wrapper( memories.list, ) @@ -513,9 +421,6 @@ class AsyncMemoriesResourceWithRawResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories - self.update = async_to_raw_response_wrapper( - memories.update, - ) self.list = async_to_raw_response_wrapper( memories.list, ) @@ -534,9 +439,6 @@ class MemoriesResourceWithStreamingResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories - self.update = to_streamed_response_wrapper( - memories.update, - ) self.list = to_streamed_response_wrapper( memories.list, ) @@ -555,9 +457,6 @@ class AsyncMemoriesResourceWithStreamingResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories - self.update = async_to_streamed_response_wrapper( - memories.update, - ) self.list = async_to_streamed_response_wrapper( memories.list, ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 2545ab27..b0199e6a 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -7,12 +7,10 @@ from .memory_add_response import MemoryAddResponse as MemoryAddResponse from .memory_get_response import MemoryGetResponse as MemoryGetResponse from .memory_list_response import MemoryListResponse as MemoryListResponse -from .memory_update_params import MemoryUpdateParams as MemoryUpdateParams from .setting_get_response import SettingGetResponse as SettingGetResponse from .search_execute_params import SearchExecuteParams as SearchExecuteParams from .setting_update_params import SettingUpdateParams as SettingUpdateParams from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse -from .memory_update_response import MemoryUpdateResponse as MemoryUpdateResponse from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse from .search_execute_response import SearchExecuteResponse as SearchExecuteResponse from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse diff --git a/src/supermemory/types/connection_create_response.py b/src/supermemory/types/connection_create_response.py index d8b52e4c..d27a6fab 100644 --- a/src/supermemory/types/connection_create_response.py +++ b/src/supermemory/types/connection_create_response.py @@ -8,8 +8,6 @@ class ConnectionCreateResponse(BaseModel): - id: str - - auth_link: str = FieldInfo(alias="authLink") - expires_in: str = FieldInfo(alias="expiresIn") + + magic_link: str = FieldInfo(alias="magicLink") diff --git a/src/supermemory/types/memory_update_params.py b/src/supermemory/types/memory_update_params.py deleted file mode 100644 index 2e94bfe3..00000000 --- a/src/supermemory/types/memory_update_params.py +++ /dev/null @@ -1,18 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, List, Union -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["MemoryUpdateParams"] - - -class MemoryUpdateParams(TypedDict, total=False): - content: Required[str] - - container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] - - metadata: Dict[str, Union[str, float, bool]] diff --git a/src/supermemory/types/memory_update_response.py b/src/supermemory/types/memory_update_response.py deleted file mode 100644 index 132b8cf9..00000000 --- a/src/supermemory/types/memory_update_response.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["MemoryUpdateResponse"] - - -class MemoryUpdateResponse(BaseModel): - id: str - - status: str diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 239662c5..7b3105cb 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -14,7 +14,6 @@ MemoryGetResponse, MemoryListResponse, MemoryDeleteResponse, - MemoryUpdateResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -23,70 +22,6 @@ class TestMemories: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() - @parametrize - def test_method_update(self, client: Supermemory) -> None: - memory = client.memories.update( - id="id", - content="This is a detailed article about machine learning concepts...", - ) - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_update_with_all_params(self, client: Supermemory) -> None: - memory = client.memories.update( - id="id", - content="This is a detailed article about machine learning concepts...", - container_tags=["string"], - metadata={ - "source": "web", - "category": "technology", - "tag_1": "ai", - "tag_2": "machine-learning", - "readingTime": 5, - "isPublic": True, - }, - ) - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_update(self, client: Supermemory) -> None: - response = client.memories.with_raw_response.update( - id="id", - content="This is a detailed article about machine learning concepts...", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = response.parse() - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_update(self, client: Supermemory) -> None: - with client.memories.with_streaming_response.update( - id="id", - content="This is a detailed article about machine learning concepts...", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = response.parse() - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - def test_path_params_update(self, client: Supermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - client.memories.with_raw_response.update( - id="", - content="This is a detailed article about machine learning concepts...", - ) - @pytest.mark.skip() @parametrize def test_method_list(self, client: Supermemory) -> None: @@ -266,70 +201,6 @@ def test_path_params_get(self, client: Supermemory) -> None: class TestAsyncMemories: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - @pytest.mark.skip() - @parametrize - async def test_method_update(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memories.update( - id="id", - content="This is a detailed article about machine learning concepts...", - ) - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memories.update( - id="id", - content="This is a detailed article about machine learning concepts...", - container_tags=["string"], - metadata={ - "source": "web", - "category": "technology", - "tag_1": "ai", - "tag_2": "machine-learning", - "readingTime": 5, - "isPublic": True, - }, - ) - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_update(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memories.with_raw_response.update( - id="id", - content="This is a detailed article about machine learning concepts...", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = await response.parse() - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_update(self, async_client: AsyncSupermemory) -> None: - async with async_client.memories.with_streaming_response.update( - id="id", - content="This is a detailed article about machine learning concepts...", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = await response.parse() - assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip() - @parametrize - async def test_path_params_update(self, async_client: AsyncSupermemory) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): - await async_client.memories.with_raw_response.update( - id="", - content="This is a detailed article about machine learning concepts...", - ) - @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncSupermemory) -> None: From 063cf45fced039b32de6f00fc4862a873d50451a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 17:17:08 +0000 Subject: [PATCH 17/24] feat(api): api update --- .stats.yml | 6 +- api.md | 2 + src/supermemory/resources/memories.py | 103 +++++++++++++- src/supermemory/types/__init__.py | 2 + .../types/connection_create_response.py | 6 +- src/supermemory/types/memory_update_params.py | 18 +++ .../types/memory_update_response.py | 11 ++ tests/api_resources/test_memories.py | 129 ++++++++++++++++++ 8 files changed, 271 insertions(+), 6 deletions(-) create mode 100644 src/supermemory/types/memory_update_params.py create mode 100644 src/supermemory/types/memory_update_response.py diff --git a/.stats.yml b/.stats.yml index a0e7cabb..213ca130 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 10 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-3fa2583744becce1e91ec5ad18f45d2cf17778def3a8f70537a15b08c746c2fb.yml -openapi_spec_hash: bf3c5827e7ddb8b32435aeb671fe7845 +configured_endpoints: 11 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-7db7d7a82f3e3d16db6481789ab9d65a18481ed036db6fcc1ef993f4a3e0e255.yml +openapi_spec_hash: 3a969e6bf3d693c02ba142fcd8e64dd8 config_hash: c8c1f2b0d63387d621f0cf066ae3379f diff --git a/api.md b/api.md index 8878df76..5e185be0 100644 --- a/api.md +++ b/api.md @@ -4,6 +4,7 @@ Types: ```python from supermemory.types import ( + MemoryUpdateResponse, MemoryListResponse, MemoryDeleteResponse, MemoryAddResponse, @@ -13,6 +14,7 @@ from supermemory.types import ( Methods: +- client.memories.update(id, \*\*params) -> MemoryUpdateResponse - client.memories.list(\*\*params) -> MemoryListResponse - client.memories.delete(id) -> MemoryDeleteResponse - client.memories.add(\*\*params) -> MemoryAddResponse diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index 517c98ee..e30e6e57 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -7,7 +7,7 @@ import httpx -from ..types import memory_add_params, memory_list_params +from ..types import memory_add_params, memory_list_params, memory_update_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property @@ -23,6 +23,7 @@ from ..types.memory_get_response import MemoryGetResponse from ..types.memory_list_response import MemoryListResponse from ..types.memory_delete_response import MemoryDeleteResponse +from ..types.memory_update_response import MemoryUpdateResponse __all__ = ["MemoriesResource", "AsyncMemoriesResource"] @@ -47,6 +48,50 @@ def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: """ return MemoriesResourceWithStreamingResponse(self) + def update( + self, + id: str, + *, + content: str, + container_tags: List[str] | NotGiven = NOT_GIVEN, + metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, + # 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, + ) -> MemoryUpdateResponse: + """ + Update a memory with any content type (text, url, file, etc.) and metadata + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return self._patch( + f"/v3/memories/{id}", + body=maybe_transform( + { + "content": content, + "container_tags": container_tags, + "metadata": metadata, + }, + memory_update_params.MemoryUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryUpdateResponse, + ) + def list( self, *, @@ -233,6 +278,50 @@ def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: """ return AsyncMemoriesResourceWithStreamingResponse(self) + async def update( + self, + id: str, + *, + content: str, + container_tags: List[str] | NotGiven = NOT_GIVEN, + metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, + # 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, + ) -> MemoryUpdateResponse: + """ + Update a memory with any content type (text, url, file, etc.) and metadata + + 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 id: + raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + return await self._patch( + f"/v3/memories/{id}", + body=await async_maybe_transform( + { + "content": content, + "container_tags": container_tags, + "metadata": metadata, + }, + memory_update_params.MemoryUpdateParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryUpdateResponse, + ) + async def list( self, *, @@ -403,6 +492,9 @@ class MemoriesResourceWithRawResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories + self.update = to_raw_response_wrapper( + memories.update, + ) self.list = to_raw_response_wrapper( memories.list, ) @@ -421,6 +513,9 @@ class AsyncMemoriesResourceWithRawResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories + self.update = async_to_raw_response_wrapper( + memories.update, + ) self.list = async_to_raw_response_wrapper( memories.list, ) @@ -439,6 +534,9 @@ class MemoriesResourceWithStreamingResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories + self.update = to_streamed_response_wrapper( + memories.update, + ) self.list = to_streamed_response_wrapper( memories.list, ) @@ -457,6 +555,9 @@ class AsyncMemoriesResourceWithStreamingResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories + self.update = async_to_streamed_response_wrapper( + memories.update, + ) self.list = async_to_streamed_response_wrapper( memories.list, ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index b0199e6a..2545ab27 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -7,10 +7,12 @@ from .memory_add_response import MemoryAddResponse as MemoryAddResponse from .memory_get_response import MemoryGetResponse as MemoryGetResponse from .memory_list_response import MemoryListResponse as MemoryListResponse +from .memory_update_params import MemoryUpdateParams as MemoryUpdateParams from .setting_get_response import SettingGetResponse as SettingGetResponse from .search_execute_params import SearchExecuteParams as SearchExecuteParams from .setting_update_params import SettingUpdateParams as SettingUpdateParams from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse +from .memory_update_response import MemoryUpdateResponse as MemoryUpdateResponse from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse from .search_execute_response import SearchExecuteResponse as SearchExecuteResponse from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse diff --git a/src/supermemory/types/connection_create_response.py b/src/supermemory/types/connection_create_response.py index d27a6fab..d8b52e4c 100644 --- a/src/supermemory/types/connection_create_response.py +++ b/src/supermemory/types/connection_create_response.py @@ -8,6 +8,8 @@ class ConnectionCreateResponse(BaseModel): - expires_in: str = FieldInfo(alias="expiresIn") + id: str + + auth_link: str = FieldInfo(alias="authLink") - magic_link: str = FieldInfo(alias="magicLink") + expires_in: str = FieldInfo(alias="expiresIn") diff --git a/src/supermemory/types/memory_update_params.py b/src/supermemory/types/memory_update_params.py new file mode 100644 index 00000000..2e94bfe3 --- /dev/null +++ b/src/supermemory/types/memory_update_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, List, Union +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["MemoryUpdateParams"] + + +class MemoryUpdateParams(TypedDict, total=False): + content: Required[str] + + container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + + metadata: Dict[str, Union[str, float, bool]] diff --git a/src/supermemory/types/memory_update_response.py b/src/supermemory/types/memory_update_response.py new file mode 100644 index 00000000..132b8cf9 --- /dev/null +++ b/src/supermemory/types/memory_update_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["MemoryUpdateResponse"] + + +class MemoryUpdateResponse(BaseModel): + id: str + + status: str diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 7b3105cb..239662c5 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -14,6 +14,7 @@ MemoryGetResponse, MemoryListResponse, MemoryDeleteResponse, + MemoryUpdateResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -22,6 +23,70 @@ class TestMemories: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() + @parametrize + def test_method_update(self, client: Supermemory) -> None: + memory = client.memories.update( + id="id", + content="This is a detailed article about machine learning concepts...", + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_update_with_all_params(self, client: Supermemory) -> None: + memory = client.memories.update( + id="id", + content="This is a detailed article about machine learning concepts...", + container_tags=["string"], + metadata={ + "source": "web", + "category": "technology", + "tag_1": "ai", + "tag_2": "machine-learning", + "readingTime": 5, + "isPublic": True, + }, + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_update(self, client: Supermemory) -> None: + response = client.memories.with_raw_response.update( + id="id", + content="This is a detailed article about machine learning concepts...", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_update(self, client: Supermemory) -> None: + with client.memories.with_streaming_response.update( + id="id", + content="This is a detailed article about machine learning concepts...", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + def test_path_params_update(self, client: Supermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + client.memories.with_raw_response.update( + id="", + content="This is a detailed article about machine learning concepts...", + ) + @pytest.mark.skip() @parametrize def test_method_list(self, client: Supermemory) -> None: @@ -201,6 +266,70 @@ def test_path_params_get(self, client: Supermemory) -> None: class TestAsyncMemories: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + @pytest.mark.skip() + @parametrize + async def test_method_update(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.update( + id="id", + content="This is a detailed article about machine learning concepts...", + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_update_with_all_params(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.update( + id="id", + content="This is a detailed article about machine learning concepts...", + container_tags=["string"], + metadata={ + "source": "web", + "category": "technology", + "tag_1": "ai", + "tag_2": "machine-learning", + "readingTime": 5, + "isPublic": True, + }, + ) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_update(self, async_client: AsyncSupermemory) -> None: + response = await async_client.memories.with_raw_response.update( + id="id", + content="This is a detailed article about machine learning concepts...", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_update(self, async_client: AsyncSupermemory) -> None: + async with async_client.memories.with_streaming_response.update( + id="id", + content="This is a detailed article about machine learning concepts...", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip() + @parametrize + async def test_path_params_update(self, async_client: AsyncSupermemory) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `id` but received ''"): + await async_client.memories.with_raw_response.update( + id="", + content="This is a detailed article about machine learning concepts...", + ) + @pytest.mark.skip() @parametrize async def test_method_list(self, async_client: AsyncSupermemory) -> None: From c0150acac7ccae7e89707909d9250028fc4d5251 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 16:17:09 +0000 Subject: [PATCH 18/24] feat(api): api update --- .stats.yml | 4 +- api.md | 4 +- src/supermemory/resources/connections.py | 77 +++++++++++++++++-- src/supermemory/types/__init__.py | 2 + .../types/connection_create_params.py | 18 +++++ .../types/connection_create_response.py | 4 + .../types/connection_list_params.py | 13 ++++ src/supermemory/types/memory_list_response.py | 22 ------ tests/api_resources/test_connections.py | 56 ++++++++++++-- 9 files changed, 161 insertions(+), 39 deletions(-) create mode 100644 src/supermemory/types/connection_create_params.py create mode 100644 src/supermemory/types/connection_list_params.py diff --git a/.stats.yml b/.stats.yml index 213ca130..df736b03 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-7db7d7a82f3e3d16db6481789ab9d65a18481ed036db6fcc1ef993f4a3e0e255.yml -openapi_spec_hash: 3a969e6bf3d693c02ba142fcd8e64dd8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-17ab2bb871e55eda916736277838e3b8bbd6cec59a3681783bdc454b9c4ab958.yml +openapi_spec_hash: 4df685b7bc763e16de83eb28da61cdf3 config_hash: c8c1f2b0d63387d621f0cf066ae3379f diff --git a/api.md b/api.md index 5e185be0..488b7488 100644 --- a/api.md +++ b/api.md @@ -59,6 +59,6 @@ from supermemory.types import ( Methods: -- client.connections.create(provider) -> ConnectionCreateResponse -- client.connections.list() -> ConnectionListResponse +- client.connections.create(provider, \*\*params) -> ConnectionCreateResponse +- client.connections.list(\*\*params) -> ConnectionListResponse - client.connections.get(connection_id) -> ConnectionGetResponse diff --git a/src/supermemory/resources/connections.py b/src/supermemory/resources/connections.py index 75181a0d..dfc95e77 100644 --- a/src/supermemory/resources/connections.py +++ b/src/supermemory/resources/connections.py @@ -2,11 +2,14 @@ from __future__ import annotations +from typing import Dict, Union, Optional from typing_extensions import Literal import httpx +from ..types import connection_list_params, connection_create_params from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -47,6 +50,9 @@ def create( self, provider: Literal["notion", "google-drive", "onedrive"], *, + end_user_id: str | NotGiven = NOT_GIVEN, + redirect_url: str | NotGiven = NOT_GIVEN, + metadata: Optional[Dict[str, Union[str, float, bool]]] | NotGiven = NOT_GIVEN, # 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, @@ -70,8 +76,19 @@ def create( raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") return self._post( f"/v3/connections/{provider}", + body=maybe_transform({"metadata": metadata}, connection_create_params.ConnectionCreateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "end_user_id": end_user_id, + "redirect_url": redirect_url, + }, + connection_create_params.ConnectionCreateParams, + ), ), cast_to=ConnectionCreateResponse, ) @@ -79,6 +96,7 @@ def create( def list( self, *, + end_user_id: str | NotGiven = NOT_GIVEN, # 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, @@ -86,11 +104,26 @@ def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ConnectionListResponse: - """List all connections""" + """ + List all connections + + 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 + """ return self._get( "/v3/connections", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform({"end_user_id": end_user_id}, connection_list_params.ConnectionListParams), ), cast_to=ConnectionListResponse, ) @@ -153,6 +186,9 @@ async def create( self, provider: Literal["notion", "google-drive", "onedrive"], *, + end_user_id: str | NotGiven = NOT_GIVEN, + redirect_url: str | NotGiven = NOT_GIVEN, + metadata: Optional[Dict[str, Union[str, float, bool]]] | NotGiven = NOT_GIVEN, # 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, @@ -176,8 +212,19 @@ async def create( raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") return await self._post( f"/v3/connections/{provider}", + body=await async_maybe_transform({"metadata": metadata}, connection_create_params.ConnectionCreateParams), options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + { + "end_user_id": end_user_id, + "redirect_url": redirect_url, + }, + connection_create_params.ConnectionCreateParams, + ), ), cast_to=ConnectionCreateResponse, ) @@ -185,6 +232,7 @@ async def create( async def list( self, *, + end_user_id: str | NotGiven = NOT_GIVEN, # 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, @@ -192,11 +240,28 @@ async def list( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ConnectionListResponse: - """List all connections""" + """ + List all connections + + 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 + """ return await self._get( "/v3/connections", options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=await async_maybe_transform( + {"end_user_id": end_user_id}, connection_list_params.ConnectionListParams + ), ), cast_to=ConnectionListResponse, ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 2545ab27..311cce3c 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -11,10 +11,12 @@ from .setting_get_response import SettingGetResponse as SettingGetResponse from .search_execute_params import SearchExecuteParams as SearchExecuteParams from .setting_update_params import SettingUpdateParams as SettingUpdateParams +from .connection_list_params import ConnectionListParams as ConnectionListParams from .memory_delete_response import MemoryDeleteResponse as MemoryDeleteResponse from .memory_update_response import MemoryUpdateResponse as MemoryUpdateResponse from .connection_get_response import ConnectionGetResponse as ConnectionGetResponse from .search_execute_response import SearchExecuteResponse as SearchExecuteResponse from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse +from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams from .connection_list_response import ConnectionListResponse as ConnectionListResponse from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse diff --git a/src/supermemory/types/connection_create_params.py b/src/supermemory/types/connection_create_params.py new file mode 100644 index 00000000..152cbce3 --- /dev/null +++ b/src/supermemory/types/connection_create_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Union, Optional +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ConnectionCreateParams"] + + +class ConnectionCreateParams(TypedDict, total=False): + end_user_id: Annotated[str, PropertyInfo(alias="endUserId")] + + redirect_url: Annotated[str, PropertyInfo(alias="redirectUrl")] + + metadata: Optional[Dict[str, Union[str, float, bool]]] diff --git a/src/supermemory/types/connection_create_response.py b/src/supermemory/types/connection_create_response.py index d8b52e4c..f0bf02e4 100644 --- a/src/supermemory/types/connection_create_response.py +++ b/src/supermemory/types/connection_create_response.py @@ -1,5 +1,7 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Optional + from pydantic import Field as FieldInfo from .._models import BaseModel @@ -13,3 +15,5 @@ class ConnectionCreateResponse(BaseModel): auth_link: str = FieldInfo(alias="authLink") expires_in: str = FieldInfo(alias="expiresIn") + + redirects_to: Optional[str] = FieldInfo(alias="redirectsTo", default=None) diff --git a/src/supermemory/types/connection_list_params.py b/src/supermemory/types/connection_list_params.py new file mode 100644 index 00000000..e027d5f1 --- /dev/null +++ b/src/supermemory/types/connection_list_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["ConnectionListParams"] + + +class ConnectionListParams(TypedDict, total=False): + end_user_id: Annotated[str, PropertyInfo(alias="endUserId")] diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index 5b325c78..e693ab3f 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -1,8 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Union, Optional -from datetime import datetime -from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -13,7 +11,6 @@ class Memory(BaseModel): id: str - """Unique identifier of the memory.""" content: Optional[str] = None """The content to extract and process into a memory. @@ -27,9 +24,6 @@ class Memory(BaseModel): We automatically detect the content type from the url's response format. """ - created_at: datetime = FieldInfo(alias="createdAt") - """Creation timestamp""" - custom_id: Optional[str] = FieldInfo(alias="customId", default=None) """Optional custom ID of the memory. @@ -49,24 +43,11 @@ class Memory(BaseModel): source: Optional[str] = None - status: Literal["unknown", "queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"] - """Status of the memory""" - summary: Optional[str] = None - """Summary of the memory content""" title: Optional[str] = None """Title of the memory""" - type: Literal["text", "pdf", "tweet", "google_doc", "image", "video", "notion_doc", "webpage"] - """Type of the memory""" - - updated_at: datetime = FieldInfo(alias="updatedAt") - """Last update timestamp""" - - url: Optional[str] = None - """URL of the memory""" - container_tags: Optional[List[str]] = FieldInfo(alias="containerTags", default=None) """Optional tags this memory should be containerized by. @@ -74,9 +55,6 @@ class Memory(BaseModel): to use to group memories. """ - raw: None = None - """Raw content of the memory""" - class Pagination(BaseModel): current_page: float = FieldInfo(alias="currentPage") diff --git a/tests/api_resources/test_connections.py b/tests/api_resources/test_connections.py index 4a34deed..aee15f42 100644 --- a/tests/api_resources/test_connections.py +++ b/tests/api_resources/test_connections.py @@ -9,7 +9,11 @@ from supermemory import Supermemory, AsyncSupermemory from tests.utils import assert_matches_type -from supermemory.types import ConnectionGetResponse, ConnectionListResponse, ConnectionCreateResponse +from supermemory.types import ( + ConnectionGetResponse, + ConnectionListResponse, + ConnectionCreateResponse, +) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -21,7 +25,18 @@ class TestConnections: @parametrize def test_method_create(self, client: Supermemory) -> None: connection = client.connections.create( - "notion", + provider="notion", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_method_create_with_all_params(self, client: Supermemory) -> None: + connection = client.connections.create( + provider="notion", + end_user_id="endUserId", + redirect_url="redirectUrl", + metadata={"foo": "string"}, ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @@ -29,7 +44,7 @@ def test_method_create(self, client: Supermemory) -> None: @parametrize def test_raw_response_create(self, client: Supermemory) -> None: response = client.connections.with_raw_response.create( - "notion", + provider="notion", ) assert response.is_closed is True @@ -41,7 +56,7 @@ def test_raw_response_create(self, client: Supermemory) -> None: @parametrize def test_streaming_response_create(self, client: Supermemory) -> None: with client.connections.with_streaming_response.create( - "notion", + provider="notion", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -57,6 +72,14 @@ def test_method_list(self, client: Supermemory) -> None: connection = client.connections.list() assert_matches_type(ConnectionListResponse, connection, path=["response"]) + @pytest.mark.skip() + @parametrize + def test_method_list_with_all_params(self, client: Supermemory) -> None: + connection = client.connections.list( + end_user_id="endUserId", + ) + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Supermemory) -> None: @@ -129,7 +152,18 @@ class TestAsyncConnections: @parametrize async def test_method_create(self, async_client: AsyncSupermemory) -> None: connection = await async_client.connections.create( - "notion", + provider="notion", + ) + assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_method_create_with_all_params(self, async_client: AsyncSupermemory) -> None: + connection = await async_client.connections.create( + provider="notion", + end_user_id="endUserId", + redirect_url="redirectUrl", + metadata={"foo": "string"}, ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @@ -137,7 +171,7 @@ async def test_method_create(self, async_client: AsyncSupermemory) -> None: @parametrize async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None: response = await async_client.connections.with_raw_response.create( - "notion", + provider="notion", ) assert response.is_closed is True @@ -149,7 +183,7 @@ async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None @parametrize async def test_streaming_response_create(self, async_client: AsyncSupermemory) -> None: async with async_client.connections.with_streaming_response.create( - "notion", + provider="notion", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -165,6 +199,14 @@ async def test_method_list(self, async_client: AsyncSupermemory) -> None: connection = await async_client.connections.list() assert_matches_type(ConnectionListResponse, connection, path=["response"]) + @pytest.mark.skip() + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncSupermemory) -> None: + connection = await async_client.connections.list( + end_user_id="endUserId", + ) + assert_matches_type(ConnectionListResponse, connection, path=["response"]) + @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: From aaf354623319d2e3f99031669a615d3af48c9dab Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 14 May 2025 06:16:58 +0000 Subject: [PATCH 19/24] feat(api): api update --- .stats.yml | 4 ++-- src/supermemory/types/memory_list_response.py | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index df736b03..ce2647c3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 11 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-17ab2bb871e55eda916736277838e3b8bbd6cec59a3681783bdc454b9c4ab958.yml -openapi_spec_hash: 4df685b7bc763e16de83eb28da61cdf3 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-e6735b03c258b382c527550bb78042bdc3aad32a5cf564785dcb9f3fb13a2862.yml +openapi_spec_hash: 8168fb51314d986893554e1cc935ca7d config_hash: c8c1f2b0d63387d621f0cf066ae3379f diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index e693ab3f..5b325c78 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -1,6 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal from pydantic import Field as FieldInfo @@ -11,6 +13,7 @@ class Memory(BaseModel): id: str + """Unique identifier of the memory.""" content: Optional[str] = None """The content to extract and process into a memory. @@ -24,6 +27,9 @@ class Memory(BaseModel): We automatically detect the content type from the url's response format. """ + created_at: datetime = FieldInfo(alias="createdAt") + """Creation timestamp""" + custom_id: Optional[str] = FieldInfo(alias="customId", default=None) """Optional custom ID of the memory. @@ -43,11 +49,24 @@ class Memory(BaseModel): source: Optional[str] = None + status: Literal["unknown", "queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"] + """Status of the memory""" + summary: Optional[str] = None + """Summary of the memory content""" title: Optional[str] = None """Title of the memory""" + type: Literal["text", "pdf", "tweet", "google_doc", "image", "video", "notion_doc", "webpage"] + """Type of the memory""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + """Last update timestamp""" + + url: Optional[str] = None + """URL of the memory""" + container_tags: Optional[List[str]] = FieldInfo(alias="containerTags", default=None) """Optional tags this memory should be containerized by. @@ -55,6 +74,9 @@ class Memory(BaseModel): to use to group memories. """ + raw: None = None + """Raw content of the memory""" + class Pagination(BaseModel): current_page: float = FieldInfo(alias="currentPage") From a327d7ddd1836e1a15b30eb5fb33388fe2580229 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 03:05:06 +0000 Subject: [PATCH 20/24] chore(ci): upload sdks to package manager --- .github/workflows/ci.yml | 24 ++++++++++++++++++++++++ scripts/utils/upload-artifact.sh | 25 +++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100755 scripts/utils/upload-artifact.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 921b479f..71338782 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,30 @@ jobs: - name: Run lints run: ./scripts/lint + upload: + if: github.repository == 'stainless-sdks/supermemory-python' + timeout-minutes: 10 + name: upload + permissions: + contents: read + id-token: write + runs-on: depot-ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Get GitHub OIDC Token + id: github-oidc + uses: actions/github-script@v6 + with: + script: core.setOutput('github_token', await core.getIDToken()); + + - name: Upload tarball + env: + URL: https://pkg.stainless.com/s + AUTH: ${{ steps.github-oidc.outputs.github_token }} + SHA: ${{ github.sha }} + run: ./scripts/utils/upload-artifact.sh + test: timeout-minutes: 10 name: test diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..280352b5 --- /dev/null +++ b/scripts/utils/upload-artifact.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -exuo pipefail + +RESPONSE=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $AUTH" \ + -H "Content-Type: application/json") + +SIGNED_URL=$(echo "$RESPONSE" | jq -r '.url') + +if [[ "$SIGNED_URL" == "null" ]]; then + echo -e "\033[31mFailed to get signed URL.\033[0m" + exit 1 +fi + +UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ + -H "Content-Type: application/gzip" \ + --data-binary @- "$SIGNED_URL" 2>&1) + +if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then + echo -e "\033[32mUploaded build to Stainless storage.\033[0m" + echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/supermemory-python/$SHA'\033[0m" +else + echo -e "\033[31mFailed to upload artifact.\033[0m" + exit 1 +fi From 060e32620febdd50931ae4d6e692a527b36b99fe Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 02:42:08 +0000 Subject: [PATCH 21/24] chore(ci): fix installation instructions --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 280352b5..19843b3c 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: npm install 'https://pkg.stainless.com/s/supermemory-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/supermemory-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From c0d13e254d08d459edc35def2c38774ce11fcd0d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 02:50:07 +0000 Subject: [PATCH 22/24] chore(internal): codegen related update --- scripts/utils/upload-artifact.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 19843b3c..11632537 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -18,7 +18,7 @@ UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/supermemory-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install --pre 'https://pkg.stainless.com/s/supermemory-python/$SHA'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From 3fd7de29691be3303c91fd89189371a0ef7845dc Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 14:27:38 +0000 Subject: [PATCH 23/24] feat(api): manual updates --- .stats.yml | 4 +- README.md | 17 ++++ api.md | 2 + src/supermemory/_files.py | 2 +- src/supermemory/resources/memories.py | 99 ++++++++++++++++++- src/supermemory/types/__init__.py | 2 + .../types/memory_upload_file_params.py | 13 +++ .../types/memory_upload_file_response.py | 11 +++ tests/api_resources/test_memories.py | 69 +++++++++++++ 9 files changed, 212 insertions(+), 7 deletions(-) create mode 100644 src/supermemory/types/memory_upload_file_params.py create mode 100644 src/supermemory/types/memory_upload_file_response.py diff --git a/.stats.yml b/.stats.yml index ce2647c3..4be06c0d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 11 +configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-e6735b03c258b382c527550bb78042bdc3aad32a5cf564785dcb9f3fb13a2862.yml openapi_spec_hash: 8168fb51314d986893554e1cc935ca7d -config_hash: c8c1f2b0d63387d621f0cf066ae3379f +config_hash: 8477e3ee6fd596ab6ac911d052e4de79 diff --git a/README.md b/README.md index 43bb97a0..55246cf4 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,23 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from supermemory import Supermemory + +client = Supermemory() + +client.memories.upload_file( + file=Path("/path/to/file"), +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `supermemory.APIConnectionError` is raised. diff --git a/api.md b/api.md index 488b7488..66639691 100644 --- a/api.md +++ b/api.md @@ -9,6 +9,7 @@ from supermemory.types import ( MemoryDeleteResponse, MemoryAddResponse, MemoryGetResponse, + MemoryUploadFileResponse, ) ``` @@ -19,6 +20,7 @@ Methods: - client.memories.delete(id) -> MemoryDeleteResponse - client.memories.add(\*\*params) -> MemoryAddResponse - client.memories.get(id) -> MemoryGetResponse +- client.memories.upload_file(\*\*params) -> MemoryUploadFileResponse # Search diff --git a/src/supermemory/_files.py b/src/supermemory/_files.py index 715cc207..0dcf63d3 100644 --- a/src/supermemory/_files.py +++ b/src/supermemory/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/supermemoryai/python-sdk/tree/main#file-uploads" ) from None diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index e30e6e57..180f4cd6 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import Dict, List, Union +from typing import Dict, List, Union, Mapping, cast from typing_extensions import Literal import httpx -from ..types import memory_add_params, memory_list_params, memory_update_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform, async_maybe_transform +from ..types import memory_add_params, memory_list_params, memory_update_params, memory_upload_file_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes +from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -24,6 +24,7 @@ from ..types.memory_list_response import MemoryListResponse from ..types.memory_delete_response import MemoryDeleteResponse from ..types.memory_update_response import MemoryUpdateResponse +from ..types.memory_upload_file_response import MemoryUploadFileResponse __all__ = ["MemoriesResource", "AsyncMemoriesResource"] @@ -257,6 +258,45 @@ def get( cast_to=MemoryGetResponse, ) + def upload_file( + self, + *, + file: FileTypes, + # 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, + ) -> MemoryUploadFileResponse: + """ + Upload a file to be processed + + 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 + """ + body = deepcopy_minimal({"file": file}) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return self._post( + "/v3/memories/file", + body=maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryUploadFileResponse, + ) + class AsyncMemoriesResource(AsyncAPIResource): @cached_property @@ -487,6 +527,45 @@ async def get( cast_to=MemoryGetResponse, ) + async def upload_file( + self, + *, + file: FileTypes, + # 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, + ) -> MemoryUploadFileResponse: + """ + Upload a file to be processed + + 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 + """ + body = deepcopy_minimal({"file": file}) + files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) + # It should be noted that the actual Content-Type header that will be + # sent to the server will contain a `boundary` parameter, e.g. + # multipart/form-data; boundary=---abc-- + extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} + return await self._post( + "/v3/memories/file", + body=await async_maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryUploadFileResponse, + ) + class MemoriesResourceWithRawResponse: def __init__(self, memories: MemoriesResource) -> None: @@ -507,6 +586,9 @@ def __init__(self, memories: MemoriesResource) -> None: self.get = to_raw_response_wrapper( memories.get, ) + self.upload_file = to_raw_response_wrapper( + memories.upload_file, + ) class AsyncMemoriesResourceWithRawResponse: @@ -528,6 +610,9 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: self.get = async_to_raw_response_wrapper( memories.get, ) + self.upload_file = async_to_raw_response_wrapper( + memories.upload_file, + ) class MemoriesResourceWithStreamingResponse: @@ -549,6 +634,9 @@ def __init__(self, memories: MemoriesResource) -> None: self.get = to_streamed_response_wrapper( memories.get, ) + self.upload_file = to_streamed_response_wrapper( + memories.upload_file, + ) class AsyncMemoriesResourceWithStreamingResponse: @@ -570,3 +658,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: self.get = async_to_streamed_response_wrapper( memories.get, ) + self.upload_file = async_to_streamed_response_wrapper( + memories.upload_file, + ) diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 311cce3c..2bb705ae 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -19,4 +19,6 @@ from .setting_update_response import SettingUpdateResponse as SettingUpdateResponse from .connection_create_params import ConnectionCreateParams as ConnectionCreateParams from .connection_list_response import ConnectionListResponse as ConnectionListResponse +from .memory_upload_file_params import MemoryUploadFileParams as MemoryUploadFileParams from .connection_create_response import ConnectionCreateResponse as ConnectionCreateResponse +from .memory_upload_file_response import MemoryUploadFileResponse as MemoryUploadFileResponse diff --git a/src/supermemory/types/memory_upload_file_params.py b/src/supermemory/types/memory_upload_file_params.py new file mode 100644 index 00000000..aa6c082a --- /dev/null +++ b/src/supermemory/types/memory_upload_file_params.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .._types import FileTypes + +__all__ = ["MemoryUploadFileParams"] + + +class MemoryUploadFileParams(TypedDict, total=False): + file: Required[FileTypes] diff --git a/src/supermemory/types/memory_upload_file_response.py b/src/supermemory/types/memory_upload_file_response.py new file mode 100644 index 00000000..f67b958f --- /dev/null +++ b/src/supermemory/types/memory_upload_file_response.py @@ -0,0 +1,11 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from .._models import BaseModel + +__all__ = ["MemoryUploadFileResponse"] + + +class MemoryUploadFileResponse(BaseModel): + id: str + + status: str diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 239662c5..8e7130f2 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -15,6 +15,7 @@ MemoryListResponse, MemoryDeleteResponse, MemoryUpdateResponse, + MemoryUploadFileResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -262,6 +263,40 @@ def test_path_params_get(self, client: Supermemory) -> None: "", ) + @pytest.mark.skip() + @parametrize + def test_method_upload_file(self, client: Supermemory) -> None: + memory = client.memories.upload_file( + file=b"raw file contents", + ) + assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_upload_file(self, client: Supermemory) -> None: + response = client.memories.with_raw_response.upload_file( + file=b"raw file contents", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + def test_streaming_response_upload_file(self, client: Supermemory) -> None: + with client.memories.with_streaming_response.upload_file( + file=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + class TestAsyncMemories: parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) @@ -504,3 +539,37 @@ async def test_path_params_get(self, async_client: AsyncSupermemory) -> None: await async_client.memories.with_raw_response.get( "", ) + + @pytest.mark.skip() + @parametrize + async def test_method_upload_file(self, async_client: AsyncSupermemory) -> None: + memory = await async_client.memories.upload_file( + file=b"raw file contents", + ) + assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_upload_file(self, async_client: AsyncSupermemory) -> None: + response = await async_client.memories.with_raw_response.upload_file( + file=b"raw file contents", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) + + @pytest.mark.skip() + @parametrize + async def test_streaming_response_upload_file(self, async_client: AsyncSupermemory) -> None: + async with async_client.memories.with_streaming_response.upload_file( + file=b"raw file contents", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True From b6e9bc2317006d5cbbbcb3c41d280df74d9b7214 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 17 May 2025 14:28:47 +0000 Subject: [PATCH 24/24] release: 3.0.0-alpha.1 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/supermemory/_version.py | 2 +- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ba6c3483..2f23d818 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.1.0-alpha.1" + ".": "3.0.0-alpha.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 10dc4129..ecf6170f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 3.0.0-alpha.1 (2025-05-17) + +Full Changelog: [v0.1.0-alpha.1...v3.0.0-alpha.1](https://github.com/supermemoryai/python-sdk/compare/v0.1.0-alpha.1...v3.0.0-alpha.1) + +### Features + +* **api:** api update ([aaf3546](https://github.com/supermemoryai/python-sdk/commit/aaf354623319d2e3f99031669a615d3af48c9dab)) +* **api:** api update ([c0150ac](https://github.com/supermemoryai/python-sdk/commit/c0150acac7ccae7e89707909d9250028fc4d5251)) +* **api:** api update ([063cf45](https://github.com/supermemoryai/python-sdk/commit/063cf45fced039b32de6f00fc4862a873d50451a)) +* **api:** api update ([7a81e80](https://github.com/supermemoryai/python-sdk/commit/7a81e8027fd0c76041672c71345e18724e71c10f)) +* **api:** api update ([722df63](https://github.com/supermemoryai/python-sdk/commit/722df6387d8fc3b38ee892d4382b19339a4b8165)) +* **api:** api update ([4052bae](https://github.com/supermemoryai/python-sdk/commit/4052baeca12183552a9bda674e97310e77b93623)) +* **api:** api update ([ed787b1](https://github.com/supermemoryai/python-sdk/commit/ed787b1abd49ebbc4e219f90bf71511306aafb2b)) +* **api:** api update ([7c0db70](https://github.com/supermemoryai/python-sdk/commit/7c0db70ec61ccd64197c333592457b925782f1ce)) +* **api:** api update ([844d56e](https://github.com/supermemoryai/python-sdk/commit/844d56ecd40cffb441c47050e5e820051d65af7e)) +* **api:** api update ([fda6f9f](https://github.com/supermemoryai/python-sdk/commit/fda6f9f111dac7db4bba42779e967356b8615e3c)) +* **api:** api update ([2403f1d](https://github.com/supermemoryai/python-sdk/commit/2403f1da4d83266ddf49ada0103c8f5d432bf966)) +* **api:** manual updates ([3fd7de2](https://github.com/supermemoryai/python-sdk/commit/3fd7de29691be3303c91fd89189371a0ef7845dc)) +* **api:** manual updates ([3e6314d](https://github.com/supermemoryai/python-sdk/commit/3e6314dba381eb65fe644941f2cca25dfcd93d3d)) +* **api:** manual updates ([4a6b77a](https://github.com/supermemoryai/python-sdk/commit/4a6b77aa6cd55d7135e33cbfb1138d9b2d00d40a)) +* **api:** manual updates ([5b0c810](https://github.com/supermemoryai/python-sdk/commit/5b0c81086db77a2ea5922d117f4e393475d2bd03)) +* **api:** manual updates ([af34c01](https://github.com/supermemoryai/python-sdk/commit/af34c01553feba151893eea0f6a905078146424f)) +* **api:** manual updates ([637811c](https://github.com/supermemoryai/python-sdk/commit/637811c4a31cfc9d258ca8562fee1cd38fb51320)) + + +### Bug Fixes + +* **package:** support direct resource imports ([aa29842](https://github.com/supermemoryai/python-sdk/commit/aa2984202e3ff68031618847bc5a438e5a42933f)) + + +### Chores + +* **ci:** fix installation instructions ([060e326](https://github.com/supermemoryai/python-sdk/commit/060e32620febdd50931ae4d6e692a527b36b99fe)) +* **ci:** upload sdks to package manager ([a327d7d](https://github.com/supermemoryai/python-sdk/commit/a327d7ddd1836e1a15b30eb5fb33388fe2580229)) +* **internal:** avoid errors for isinstance checks on proxies ([be6c667](https://github.com/supermemoryai/python-sdk/commit/be6c667dbff65c00fc7f3bd22e541b477c19ca08)) +* **internal:** codegen related update ([c0d13e2](https://github.com/supermemoryai/python-sdk/commit/c0d13e254d08d459edc35def2c38774ce11fcd0d)) + ## 0.1.0-alpha.1 (2025-04-29) Full Changelog: [v0.0.1-alpha.0...v0.1.0-alpha.1](https://github.com/supermemoryai/python-sdk/compare/v0.0.1-alpha.0...v0.1.0-alpha.1) diff --git a/pyproject.toml b/pyproject.toml index 8427a405..9ca24d46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "supermemory" -version = "0.1.0-alpha.1" +version = "3.0.0-alpha.1" description = "The official Python library for the supermemory API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/supermemory/_version.py b/src/supermemory/_version.py index a40c4ed9..c8cc1993 100644 --- a/src/supermemory/_version.py +++ b/src/supermemory/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "supermemory" -__version__ = "0.1.0-alpha.1" # x-release-please-version +__version__ = "3.0.0-alpha.1" # x-release-please-version