From b61473253183d434613b0aeb631376262d22cb0c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 18:14:06 +0000 Subject: [PATCH 01/12] feat(api): api update --- .stats.yml | 4 +- README.md | 20 +++------ requirements-dev.lock | 2 +- requirements.lock | 2 +- src/supermemory/resources/memories.py | 40 ++++++++--------- src/supermemory/types/memory_add_params.py | 18 ++++---- src/supermemory/types/memory_update_params.py | 18 ++++---- tests/api_resources/test_memories.py | 40 +++++------------ tests/test_client.py | 44 +++++-------------- 9 files changed, 67 insertions(+), 121 deletions(-) diff --git a/.stats.yml b/.stats.yml index b801420b..70aa42d2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-1a2a84a9cc99c25a9d726bc9300a72871f9469e3ceacebd5e71fd37e87891318.yml -openapi_spec_hash: e71e86a645bc47a86664080c8697b8db +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-09da5c3ffd6340714c6cb0068dc6b708becce683de2ea324d03f402cf5113b16.yml +openapi_spec_hash: 7f6021bcb2388695b04edb0575904421 config_hash: be10c837d5319a33f30809a3ec223caf diff --git a/README.md b/README.md index c78d9389..b0f82354 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,7 @@ from supermemory import Supermemory client = Supermemory() try: - client.memories.add( - content="This is a detailed article about machine learning concepts...", - ) + client.memories.add() except supermemory.APIConnectionError as e: print("The server could not be reached") print(e.__cause__) # an underlying Exception, likely raised within httpx. @@ -172,9 +170,7 @@ client = Supermemory( ) # Or, configure per-request: -client.with_options(max_retries=5).memories.add( - content="This is a detailed article about machine learning concepts...", -) +client.with_options(max_retries=5).memories.add() ``` ### Timeouts @@ -197,9 +193,7 @@ client = Supermemory( ) # Override per-request: -client.with_options(timeout=5.0).memories.add( - content="This is a detailed article about machine learning concepts...", -) +client.with_options(timeout=5.0).memories.add() ``` On timeout, an `APITimeoutError` is thrown. @@ -240,9 +234,7 @@ 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.add( - content="This is a detailed article about machine learning concepts...", -) +response = client.memories.with_raw_response.add() print(response.headers.get('X-My-Header')) memory = response.parse() # get the object that `memories.add()` would have returned @@ -260,9 +252,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.add( - content="This is a detailed article about machine learning concepts...", -) as response: +with client.memories.with_streaming_response.add() as response: print(response.headers.get("X-My-Header")) for line in response.iter_lines(): diff --git a/requirements-dev.lock b/requirements-dev.lock index c63e536d..41390c7d 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -56,7 +56,7 @@ httpx==0.28.1 # via httpx-aiohttp # via respx # via supermemory -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via supermemory idna==3.4 # via anyio diff --git a/requirements.lock b/requirements.lock index c54f68b1..757c06df 100644 --- a/requirements.lock +++ b/requirements.lock @@ -43,7 +43,7 @@ httpcore==1.0.2 httpx==0.28.1 # via httpx-aiohttp # via supermemory -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via supermemory idna==3.4 # via anyio diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index 787edf71..b7f1ee6a 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -51,8 +51,8 @@ def update( self, id: str, *, - content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + content: str | NotGiven = NOT_GIVEN, custom_id: 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. @@ -66,6 +66,9 @@ def update( Update a memory with any content type (text, url, file, etc.) and metadata Args: + container_tags: 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. + 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. @@ -75,9 +78,6 @@ def update( We automatically detect the content type from the url's response format. - container_tags: 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. - custom_id: Optional custom ID of the memory. This could be an ID from your database that will uniquely identify this memory. @@ -101,8 +101,8 @@ def update( f"/v3/memories/{id}", body=maybe_transform( { - "content": content, "container_tags": container_tags, + "content": content, "custom_id": custom_id, "metadata": metadata, }, @@ -211,8 +211,8 @@ def delete( def add( self, *, - content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + content: str | NotGiven = NOT_GIVEN, custom_id: 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. @@ -226,6 +226,9 @@ def add( Add a memory with any content type (text, url, file, etc.) and metadata Args: + container_tags: 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. + 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. @@ -235,9 +238,6 @@ def add( We automatically detect the content type from the url's response format. - container_tags: 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. - custom_id: Optional custom ID of the memory. This could be an ID from your database that will uniquely identify this memory. @@ -259,8 +259,8 @@ def add( "/v3/memories", body=maybe_transform( { - "content": content, "container_tags": container_tags, + "content": content, "custom_id": custom_id, "metadata": metadata, }, @@ -330,8 +330,8 @@ async def update( self, id: str, *, - content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + content: str | NotGiven = NOT_GIVEN, custom_id: 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. @@ -345,6 +345,9 @@ async def update( Update a memory with any content type (text, url, file, etc.) and metadata Args: + container_tags: 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. + 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. @@ -354,9 +357,6 @@ async def update( We automatically detect the content type from the url's response format. - container_tags: 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. - custom_id: Optional custom ID of the memory. This could be an ID from your database that will uniquely identify this memory. @@ -380,8 +380,8 @@ async def update( f"/v3/memories/{id}", body=await async_maybe_transform( { - "content": content, "container_tags": container_tags, + "content": content, "custom_id": custom_id, "metadata": metadata, }, @@ -490,8 +490,8 @@ async def delete( async def add( self, *, - content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + content: str | NotGiven = NOT_GIVEN, custom_id: 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. @@ -505,6 +505,9 @@ async def add( Add a memory with any content type (text, url, file, etc.) and metadata Args: + container_tags: 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. + 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. @@ -514,9 +517,6 @@ async def add( We automatically detect the content type from the url's response format. - container_tags: 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. - custom_id: Optional custom ID of the memory. This could be an ID from your database that will uniquely identify this memory. @@ -538,8 +538,8 @@ async def add( "/v3/memories", body=await async_maybe_transform( { - "content": content, "container_tags": container_tags, + "content": content, "custom_id": custom_id, "metadata": metadata, }, diff --git a/src/supermemory/types/memory_add_params.py b/src/supermemory/types/memory_add_params.py index b010b941..d4837214 100644 --- a/src/supermemory/types/memory_add_params.py +++ b/src/supermemory/types/memory_add_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Dict, List, Union -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -11,7 +11,14 @@ class MemoryAddParams(TypedDict, total=False): - content: Required[str] + container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + """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. + """ + + content: 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. @@ -23,13 +30,6 @@ class MemoryAddParams(TypedDict, total=False): We automatically detect the content type from the url's response format. """ - container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] - """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. - """ - custom_id: Annotated[str, PropertyInfo(alias="customId")] """Optional custom ID of the memory. diff --git a/src/supermemory/types/memory_update_params.py b/src/supermemory/types/memory_update_params.py index 2dc1d45d..9bd1e71c 100644 --- a/src/supermemory/types/memory_update_params.py +++ b/src/supermemory/types/memory_update_params.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Dict, List, Union -from typing_extensions import Required, Annotated, TypedDict +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -11,7 +11,14 @@ class MemoryUpdateParams(TypedDict, total=False): - content: Required[str] + container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + """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. + """ + + content: 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. @@ -23,13 +30,6 @@ class MemoryUpdateParams(TypedDict, total=False): We automatically detect the content type from the url's response format. """ - container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] - """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. - """ - custom_id: Annotated[str, PropertyInfo(alias="customId")] """Optional custom ID of the memory. diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index a522fa8a..a7611a49 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -27,7 +27,6 @@ class TestMemories: 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"]) @@ -36,8 +35,8 @@ def test_method_update(self, client: Supermemory) -> None: 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=["user_123", "project_123"], + content="This is a detailed article about machine learning concepts...", custom_id="mem_abc123", metadata={ "category": "technology", @@ -55,7 +54,6 @@ def test_method_update_with_all_params(self, client: Supermemory) -> None: 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 @@ -68,7 +66,6 @@ def test_raw_response_update(self, client: Supermemory) -> None: 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" @@ -84,7 +81,6 @@ 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() @@ -173,17 +169,15 @@ def test_path_params_delete(self, client: Supermemory) -> None: @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...", - ) + memory = client.memories.add() assert_matches_type(MemoryAddResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize 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=["user_123", "project_123"], + content="This is a detailed article about machine learning concepts...", custom_id="mem_abc123", metadata={ "category": "technology", @@ -199,9 +193,7 @@ def test_method_add_with_all_params(self, client: Supermemory) -> None: @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...", - ) + response = client.memories.with_raw_response.add() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -211,9 +203,7 @@ def test_raw_response_add(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize 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: + with client.memories.with_streaming_response.add() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -275,7 +265,6 @@ class TestAsyncMemories: 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"]) @@ -284,8 +273,8 @@ async def test_method_update(self, async_client: AsyncSupermemory) -> None: 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=["user_123", "project_123"], + content="This is a detailed article about machine learning concepts...", custom_id="mem_abc123", metadata={ "category": "technology", @@ -303,7 +292,6 @@ async def test_method_update_with_all_params(self, async_client: AsyncSupermemor 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 @@ -316,7 +304,6 @@ async def test_raw_response_update(self, async_client: AsyncSupermemory) -> None 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" @@ -332,7 +319,6 @@ 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() @@ -421,17 +407,15 @@ async def test_path_params_delete(self, async_client: AsyncSupermemory) -> None: @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...", - ) + memory = await async_client.memories.add() assert_matches_type(MemoryAddResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize 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=["user_123", "project_123"], + content="This is a detailed article about machine learning concepts...", custom_id="mem_abc123", metadata={ "category": "technology", @@ -447,9 +431,7 @@ async def test_method_add_with_all_params(self, async_client: AsyncSupermemory) @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...", - ) + response = await async_client.memories.with_raw_response.add() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -459,9 +441,7 @@ async def test_raw_response_add(self, async_client: AsyncSupermemory) -> None: @pytest.mark.skip() @parametrize 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: + async with async_client.memories.with_streaming_response.add() 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 ecc086ec..101a597a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -727,9 +727,7 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien respx_mock.post("/v3/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.memories.with_streaming_response.add( - content="This is a detailed article about machine learning concepts..." - ).__enter__() + client.memories.with_streaming_response.add().__enter__() assert _get_open_connections(self.client) == 0 @@ -739,9 +737,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.memories.with_streaming_response.add( - content="This is a detailed article about machine learning concepts..." - ).__enter__() + client.memories.with_streaming_response.add().__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -770,9 +766,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.add( - content="This is a detailed article about machine learning concepts..." - ) + response = client.memories.with_raw_response.add() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -796,10 +790,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.add( - content="This is a detailed article about machine learning concepts...", - extra_headers={"x-stainless-retry-count": Omit()}, - ) + response = client.memories.with_raw_response.add(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -822,10 +813,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memories.with_raw_response.add( - content="This is a detailed article about machine learning concepts...", - extra_headers={"x-stainless-retry-count": "42"}, - ) + response = client.memories.with_raw_response.add(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1556,9 +1544,7 @@ async def test_retrying_timeout_errors_doesnt_leak( respx_mock.post("/v3/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.memories.with_streaming_response.add( - content="This is a detailed article about machine learning concepts..." - ).__aenter__() + await async_client.memories.with_streaming_response.add().__aenter__() assert _get_open_connections(self.client) == 0 @@ -1570,9 +1556,7 @@ async def test_retrying_status_errors_doesnt_leak( respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.memories.with_streaming_response.add( - content="This is a detailed article about machine learning concepts..." - ).__aenter__() + await async_client.memories.with_streaming_response.add().__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1602,9 +1586,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memories.with_raw_response.add( - content="This is a detailed article about machine learning concepts..." - ) + response = await client.memories.with_raw_response.add() assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1629,10 +1611,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - 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()}, - ) + response = await client.memories.with_raw_response.add(extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1656,10 +1635,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/v3/memories").mock(side_effect=retry_handler) - 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"}, - ) + response = await client.memories.with_raw_response.add(extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From 9e822a16ce8cf30791abf6384e2e3205233eeaba Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:21:27 +0000 Subject: [PATCH 02/12] chore(internal): bump pinned h11 dep --- requirements-dev.lock | 4 ++-- requirements.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 41390c7d..722cf701 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,9 +48,9 @@ filelock==3.12.4 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via httpx-aiohttp diff --git a/requirements.lock b/requirements.lock index 757c06df..22670ed7 100644 --- a/requirements.lock +++ b/requirements.lock @@ -36,9 +36,9 @@ exceptiongroup==1.2.2 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via httpx-aiohttp From 2dc73dd51ac30fa4d6b2d370b7411857518c1ddd Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:41:11 +0000 Subject: [PATCH 03/12] chore(package): mark python 3.13 as supported --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index a33b5b82..ba2bab96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", From 812e982cbba93e197d4cd3cf8bdfa710e7830a78 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 02:37:26 +0000 Subject: [PATCH 04/12] fix(parsing): correctly handle nested discriminated unions --- src/supermemory/_models.py | 13 ++++++----- tests/test_models.py | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/supermemory/_models.py b/src/supermemory/_models.py index 4f214980..528d5680 100644 --- a/src/supermemory/_models.py +++ b/src/supermemory/_models.py @@ -2,9 +2,10 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) def is_basemodel(type_: type) -> bool: @@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. @@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object: type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() diff --git a/tests/test_models.py b/tests/test_models.py index 4e795b1e..44d0a95a 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -889,3 +889,48 @@ class ModelB(BaseModel): ) assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) From 3f71f60954dedc0a91e1859df48c5c3ca0a47c88 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 07:14:05 +0000 Subject: [PATCH 05/12] feat(api): api update --- .stats.yml | 4 ++-- src/supermemory/types/memory_get_response.py | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.stats.yml b/.stats.yml index 70aa42d2..5ff773cc 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-09da5c3ffd6340714c6cb0068dc6b708becce683de2ea324d03f402cf5113b16.yml -openapi_spec_hash: 7f6021bcb2388695b04edb0575904421 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-9898601fd84ac5d7adacff7fcd839cece38093f748963d125b1609ffa5a4778a.yml +openapi_spec_hash: b1506162a216c12f5925af429aef2624 config_hash: be10c837d5319a33f30809a3ec223caf diff --git a/src/supermemory/types/memory_get_response.py b/src/supermemory/types/memory_get_response.py index 90bf8f31..335eded2 100644 --- a/src/supermemory/types/memory_get_response.py +++ b/src/supermemory/types/memory_get_response.py @@ -62,6 +62,12 @@ class MemoryGetResponse(BaseModel): summary: Optional[str] = None """Summary of the memory content""" + summary_embedding_model: Optional[str] = FieldInfo(alias="summaryEmbeddingModel", default=None) + + summary_embedding_model_new: Optional[str] = FieldInfo(alias="summaryEmbeddingModelNew", default=None) + + summary_embedding_new: Optional[List[float]] = FieldInfo(alias="summaryEmbeddingNew", default=None) + title: Optional[str] = None """Title of the memory""" @@ -83,9 +89,6 @@ class MemoryGetResponse(BaseModel): 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. @@ -95,3 +98,6 @@ class MemoryGetResponse(BaseModel): raw: None = None """Raw content of the memory""" + + url: Optional[str] = None + """URL of the memory""" From a6d7d7a100680cfaa03138542f60b7b7407ad347 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 02:55:28 +0000 Subject: [PATCH 06/12] chore(readme): fix version rendering on pypi --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b0f82354..c0cc0cae 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Supermemory Python API library -[![PyPI version]()](https://pypi.org/project/supermemory/) + +[![PyPI version](https://img.shields.io/pypi/v/supermemory.svg?label=pypi%20(stable))](https://pypi.org/project/supermemory/) The Supermemory Python library provides convenient access to the Supermemory REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, From 80480dd46271dc5136f39c5ff1315555b8d51e31 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:02:10 +0000 Subject: [PATCH 07/12] fix(client): don't send Content-Type header on GET requests --- pyproject.toml | 2 +- src/supermemory/_base_client.py | 11 +++++++++-- tests/test_client.py | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ba2bab96..ef100a4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ Homepage = "https://github.com/supermemoryai/python-sdk" Repository = "https://github.com/supermemoryai/python-sdk" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] [tool.rye] managed = true diff --git a/src/supermemory/_base_client.py b/src/supermemory/_base_client.py index 9a46a403..b0c778db 100644 --- a/src/supermemory/_base_client.py +++ b/src/supermemory/_base_client.py @@ -529,6 +529,15 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + kwargs["json"] = json_data if is_given(json_data) else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -540,8 +549,6 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data if is_given(json_data) else None, - files=files, **kwargs, ) diff --git a/tests/test_client.py b/tests/test_client.py index 101a597a..06127d83 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -464,7 +464,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Supermemory) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1275,7 +1275,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncSupermemory) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, From 86eae4481e228f741003f34f46e618c0b355c533 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Jul 2025 05:14:14 +0000 Subject: [PATCH 08/12] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5ff773cc..0a97a100 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-9898601fd84ac5d7adacff7fcd839cece38093f748963d125b1609ffa5a4778a.yml -openapi_spec_hash: b1506162a216c12f5925af429aef2624 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-886787346bfd9007dbd58542fddf6fad4592b1ccc2d1923f44378678dda7c1c1.yml +openapi_spec_hash: 1253fce7081c738f257275e9f49202b9 config_hash: be10c837d5319a33f30809a3ec223caf From 03555719bf0fe77e32b9942ac79d4f47e035c896 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sun, 13 Jul 2025 23:14:14 +0000 Subject: [PATCH 09/12] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 0a97a100..748277f2 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-886787346bfd9007dbd58542fddf6fad4592b1ccc2d1923f44378678dda7c1c1.yml -openapi_spec_hash: 1253fce7081c738f257275e9f49202b9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-129a4cca9401146ece1a81535ebfb1b6fd238cc7181460431292f6c56e89d243.yml +openapi_spec_hash: 2d7dc70490fa501b2d2e50e09353e5a2 config_hash: be10c837d5319a33f30809a3ec223caf From 228a8d985c492fc56f24e06c49fe6f08c813a972 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 01:14:15 +0000 Subject: [PATCH 10/12] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 748277f2..0a97a100 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 16 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-129a4cca9401146ece1a81535ebfb1b6fd238cc7181460431292f6c56e89d243.yml -openapi_spec_hash: 2d7dc70490fa501b2d2e50e09353e5a2 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-new-886787346bfd9007dbd58542fddf6fad4592b1ccc2d1923f44378678dda7c1c1.yml +openapi_spec_hash: 1253fce7081c738f257275e9f49202b9 config_hash: be10c837d5319a33f30809a3ec223caf From 4aaccf17ae31c04f3097fe04a6a081171fc725d1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 02:02:24 +0000 Subject: [PATCH 11/12] feat: clean up environment call outs --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index c0cc0cae..4c0fefb1 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,6 @@ pip install --pre supermemory[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python -import os import asyncio from supermemory import DefaultAioHttpClient from supermemory import AsyncSupermemory @@ -91,7 +90,7 @@ from supermemory import AsyncSupermemory async def main() -> None: async with AsyncSupermemory( - api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted + api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: response = await client.search.execute( From d7a36dcb44dab04a635f9c44e14f64afcc1172a2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 02:05:11 +0000 Subject: [PATCH 12/12] release: 3.0.0-alpha.23 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ pyproject.toml | 2 +- src/supermemory/_version.py | 2 +- 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 32634a8c..22bbc61b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.0.0-alpha.22" + ".": "3.0.0-alpha.23" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c995c2c9..b3da35b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 3.0.0-alpha.23 (2025-07-15) + +Full Changelog: [v3.0.0-alpha.22...v3.0.0-alpha.23](https://github.com/supermemoryai/python-sdk/compare/v3.0.0-alpha.22...v3.0.0-alpha.23) + +### Features + +* **api:** api update ([3f71f60](https://github.com/supermemoryai/python-sdk/commit/3f71f60954dedc0a91e1859df48c5c3ca0a47c88)) +* **api:** api update ([b614732](https://github.com/supermemoryai/python-sdk/commit/b61473253183d434613b0aeb631376262d22cb0c)) +* clean up environment call outs ([4aaccf1](https://github.com/supermemoryai/python-sdk/commit/4aaccf17ae31c04f3097fe04a6a081171fc725d1)) + + +### Bug Fixes + +* **client:** don't send Content-Type header on GET requests ([80480dd](https://github.com/supermemoryai/python-sdk/commit/80480dd46271dc5136f39c5ff1315555b8d51e31)) +* **parsing:** correctly handle nested discriminated unions ([812e982](https://github.com/supermemoryai/python-sdk/commit/812e982cbba93e197d4cd3cf8bdfa710e7830a78)) + + +### Chores + +* **internal:** bump pinned h11 dep ([9e822a1](https://github.com/supermemoryai/python-sdk/commit/9e822a16ce8cf30791abf6384e2e3205233eeaba)) +* **package:** mark python 3.13 as supported ([2dc73dd](https://github.com/supermemoryai/python-sdk/commit/2dc73dd51ac30fa4d6b2d370b7411857518c1ddd)) +* **readme:** fix version rendering on pypi ([a6d7d7a](https://github.com/supermemoryai/python-sdk/commit/a6d7d7a100680cfaa03138542f60b7b7407ad347)) + ## 3.0.0-alpha.22 (2025-07-03) Full Changelog: [v3.0.0-alpha.21...v3.0.0-alpha.22](https://github.com/supermemoryai/python-sdk/compare/v3.0.0-alpha.21...v3.0.0-alpha.22) diff --git a/pyproject.toml b/pyproject.toml index ef100a4c..b4458538 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "supermemory" -version = "3.0.0-alpha.22" +version = "3.0.0-alpha.23" 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 fe9b6240..1295b6e1 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__ = "3.0.0-alpha.22" # x-release-please-version +__version__ = "3.0.0-alpha.23" # x-release-please-version