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/.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/.stats.yml b/.stats.yml index b07d08ce..4be06c0d 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 -config_hash: eb32087403f958eead829e810f5a71b8 +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: 8477e3ee6fd596ab6ac911d052e4de79 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/README.md b/README.md index 3dfd7e8c..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. @@ -93,7 +110,7 @@ from supermemory import Supermemory client = Supermemory() try: - client.memory.create( + client.memories.add( content="This is a detailed article about machine learning concepts...", ) except supermemory.APIConnectionError as e: @@ -138,7 +155,7 @@ client = Supermemory( ) # Or, configure per-request: -client.with_options(max_retries=5).memory.create( +client.with_options(max_retries=5).memories.add( content="This is a detailed article about machine learning concepts...", ) ``` @@ -163,7 +180,7 @@ client = Supermemory( ) # Override per-request: -client.with_options(timeout=5.0).memory.create( +client.with_options(timeout=5.0).memories.add( content="This is a detailed article about machine learning concepts...", ) ``` @@ -206,12 +223,12 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from supermemory import Supermemory client = Supermemory() -response = client.memory.with_raw_response.create( +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 `memory.create()` would have returned +memory = response.parse() # get the object that `memories.add()` would have returned print(memory.id) ``` @@ -226,7 +243,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.memory.with_streaming_response.create( +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 337614f6..66639691 100644 --- a/api.md +++ b/api.md @@ -1,56 +1,66 @@ -# Settings +# Memories Types: ```python -from supermemory.types import SettingUpdateResponse +from supermemory.types import ( + MemoryUpdateResponse, + MemoryListResponse, + MemoryDeleteResponse, + MemoryAddResponse, + MemoryGetResponse, + MemoryUploadFileResponse, +) ``` Methods: -- client.settings.update(\*\*params) -> SettingUpdateResponse +- client.memories.update(id, \*\*params) -> MemoryUpdateResponse +- client.memories.list(\*\*params) -> MemoryListResponse +- client.memories.delete(id) -> MemoryDeleteResponse +- client.memories.add(\*\*params) -> MemoryAddResponse +- client.memories.get(id) -> MemoryGetResponse +- client.memories.upload_file(\*\*params) -> MemoryUploadFileResponse -# Memory +# Search Types: ```python -from supermemory.types import ( - MemoryCreateResponse, - MemoryListResponse, - MemoryDeleteResponse, - MemoryGetResponse, -) +from supermemory.types import SearchExecuteResponse ``` Methods: -- client.memory.create(\*\*params) -> MemoryCreateResponse -- client.memory.list(\*\*params) -> MemoryListResponse -- client.memory.delete(id) -> MemoryDeleteResponse -- client.memory.get(id) -> MemoryGetResponse +- client.search.execute(\*\*params) -> SearchExecuteResponse -# 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, \*\*params) -> ConnectionCreateResponse +- client.connections.list(\*\*params) -> ConnectionListResponse +- client.connections.get(connection_id) -> ConnectionGetResponse 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/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh new file mode 100755 index 00000000..11632537 --- /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: 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 +fi 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/_client.py b/src/supermemory/_client.py index 10d8d620..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 memory, search, settings, connection +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 ( @@ -43,10 +43,10 @@ class Supermemory(SyncAPIClient): - settings: settings.SettingsResource - memory: memory.MemoryResource + memories: memories.MemoriesResource search: search.SearchResource - connection: connection.ConnectionResource + settings: settings.SettingsResource + connections: connections.ConnectionsResource with_raw_response: SupermemoryWithRawResponse with_streaming_response: SupermemoryWithStreamedResponse @@ -91,7 +91,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 +104,10 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.settings = settings.SettingsResource(self) - self.memory = memory.MemoryResource(self) + self.memories = memories.MemoriesResource(self) self.search = search.SearchResource(self) - self.connection = connection.ConnectionResource(self) + self.settings = settings.SettingsResource(self) + self.connections = connections.ConnectionsResource(self) self.with_raw_response = SupermemoryWithRawResponse(self) self.with_streaming_response = SupermemoryWithStreamedResponse(self) @@ -120,7 +120,7 @@ def qs(self) -> Querystring: @override def auth_headers(self) -> dict[str, str]: api_key = self.api_key - return {"X-API-Key": api_key} + return {"Authorization": f"Bearer {api_key}"} @property @override @@ -217,10 +217,10 @@ def _make_status_error( class AsyncSupermemory(AsyncAPIClient): - settings: settings.AsyncSettingsResource - memory: memory.AsyncMemoryResource + memories: memories.AsyncMemoriesResource search: search.AsyncSearchResource - connection: connection.AsyncConnectionResource + settings: settings.AsyncSettingsResource + connections: connections.AsyncConnectionsResource with_raw_response: AsyncSupermemoryWithRawResponse with_streaming_response: AsyncSupermemoryWithStreamedResponse @@ -265,7 +265,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__, @@ -278,10 +278,10 @@ def __init__( _strict_response_validation=_strict_response_validation, ) - self.settings = settings.AsyncSettingsResource(self) - self.memory = memory.AsyncMemoryResource(self) + self.memories = memories.AsyncMemoriesResource(self) self.search = search.AsyncSearchResource(self) - self.connection = connection.AsyncConnectionResource(self) + self.settings = settings.AsyncSettingsResource(self) + self.connections = connections.AsyncConnectionsResource(self) self.with_raw_response = AsyncSupermemoryWithRawResponse(self) self.with_streaming_response = AsyncSupermemoryWithStreamedResponse(self) @@ -294,7 +294,7 @@ def qs(self) -> Querystring: @override def auth_headers(self) -> dict[str, str]: api_key = self.api_key - return {"X-API-Key": api_key} + return {"Authorization": f"Bearer {api_key}"} @property @override @@ -392,34 +392,34 @@ def _make_status_error( class SupermemoryWithRawResponse: def __init__(self, client: Supermemory) -> None: - self.settings = settings.SettingsResourceWithRawResponse(client.settings) - self.memory = memory.MemoryResourceWithRawResponse(client.memory) + self.memories = memories.MemoriesResourceWithRawResponse(client.memories) self.search = search.SearchResourceWithRawResponse(client.search) - self.connection = connection.ConnectionResourceWithRawResponse(client.connection) + self.settings = settings.SettingsResourceWithRawResponse(client.settings) + self.connections = connections.ConnectionsResourceWithRawResponse(client.connections) class AsyncSupermemoryWithRawResponse: def __init__(self, client: AsyncSupermemory) -> None: - self.settings = settings.AsyncSettingsResourceWithRawResponse(client.settings) - self.memory = memory.AsyncMemoryResourceWithRawResponse(client.memory) + self.memories = memories.AsyncMemoriesResourceWithRawResponse(client.memories) self.search = search.AsyncSearchResourceWithRawResponse(client.search) - self.connection = connection.AsyncConnectionResourceWithRawResponse(client.connection) + self.settings = settings.AsyncSettingsResourceWithRawResponse(client.settings) + self.connections = connections.AsyncConnectionsResourceWithRawResponse(client.connections) class SupermemoryWithStreamedResponse: def __init__(self, client: Supermemory) -> None: - self.settings = settings.SettingsResourceWithStreamingResponse(client.settings) - self.memory = memory.MemoryResourceWithStreamingResponse(client.memory) + self.memories = memories.MemoriesResourceWithStreamingResponse(client.memories) self.search = search.SearchResourceWithStreamingResponse(client.search) - self.connection = connection.ConnectionResourceWithStreamingResponse(client.connection) + self.settings = settings.SettingsResourceWithStreamingResponse(client.settings) + self.connections = connections.ConnectionsResourceWithStreamingResponse(client.connections) class AsyncSupermemoryWithStreamedResponse: def __init__(self, client: AsyncSupermemory) -> None: - self.settings = settings.AsyncSettingsResourceWithStreamingResponse(client.settings) - self.memory = memory.AsyncMemoryResourceWithStreamingResponse(client.memory) + self.memories = memories.AsyncMemoriesResourceWithStreamingResponse(client.memories) self.search = search.AsyncSearchResourceWithStreamingResponse(client.search) - self.connection = connection.AsyncConnectionResourceWithStreamingResponse(client.connection) + self.settings = settings.AsyncSettingsResourceWithStreamingResponse(client.settings) + self.connections = connections.AsyncConnectionsResourceWithStreamingResponse(client.connections) Client = Supermemory 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/_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/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__() 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 diff --git a/src/supermemory/resources/__init__.py b/src/supermemory/resources/__init__.py index b772e05e..275ecfbe 100644 --- a/src/supermemory/resources/__init__.py +++ b/src/supermemory/resources/__init__.py @@ -1,13 +1,5 @@ # 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, @@ -16,6 +8,14 @@ SearchResourceWithStreamingResponse, AsyncSearchResourceWithStreamingResponse, ) +from .memories import ( + MemoriesResource, + AsyncMemoriesResource, + MemoriesResourceWithRawResponse, + AsyncMemoriesResourceWithRawResponse, + MemoriesResourceWithStreamingResponse, + AsyncMemoriesResourceWithStreamingResponse, +) from .settings import ( SettingsResource, AsyncSettingsResource, @@ -24,38 +24,38 @@ SettingsResourceWithStreamingResponse, AsyncSettingsResourceWithStreamingResponse, ) -from .connection import ( - ConnectionResource, - AsyncConnectionResource, - ConnectionResourceWithRawResponse, - AsyncConnectionResourceWithRawResponse, - ConnectionResourceWithStreamingResponse, - AsyncConnectionResourceWithStreamingResponse, +from .connections import ( + ConnectionsResource, + AsyncConnectionsResource, + ConnectionsResourceWithRawResponse, + AsyncConnectionsResourceWithRawResponse, + ConnectionsResourceWithStreamingResponse, + AsyncConnectionsResourceWithStreamingResponse, ) __all__ = [ - "SettingsResource", - "AsyncSettingsResource", - "SettingsResourceWithRawResponse", - "AsyncSettingsResourceWithRawResponse", - "SettingsResourceWithStreamingResponse", - "AsyncSettingsResourceWithStreamingResponse", - "MemoryResource", - "AsyncMemoryResource", - "MemoryResourceWithRawResponse", - "AsyncMemoryResourceWithRawResponse", - "MemoryResourceWithStreamingResponse", - "AsyncMemoryResourceWithStreamingResponse", + "MemoriesResource", + "AsyncMemoriesResource", + "MemoriesResourceWithRawResponse", + "AsyncMemoriesResourceWithRawResponse", + "MemoriesResourceWithStreamingResponse", + "AsyncMemoriesResourceWithStreamingResponse", "SearchResource", "AsyncSearchResource", "SearchResourceWithRawResponse", "AsyncSearchResourceWithRawResponse", "SearchResourceWithStreamingResponse", "AsyncSearchResourceWithStreamingResponse", - "ConnectionResource", - "AsyncConnectionResource", - "ConnectionResourceWithRawResponse", - "AsyncConnectionResourceWithRawResponse", - "ConnectionResourceWithStreamingResponse", - "AsyncConnectionResourceWithStreamingResponse", + "SettingsResource", + "AsyncSettingsResource", + "SettingsResourceWithRawResponse", + "AsyncSettingsResourceWithRawResponse", + "SettingsResourceWithStreamingResponse", + "AsyncSettingsResourceWithStreamingResponse", + "ConnectionsResource", + "AsyncConnectionsResource", + "ConnectionsResourceWithRawResponse", + "AsyncConnectionsResourceWithRawResponse", + "ConnectionsResourceWithStreamingResponse", + "AsyncConnectionsResourceWithStreamingResponse", ] diff --git a/src/supermemory/resources/connection.py b/src/supermemory/resources/connections.py similarity index 52% rename from src/supermemory/resources/connection.py rename to src/supermemory/resources/connections.py index a55ea8b7..dfc95e77 100644 --- a/src/supermemory/resources/connection.py +++ b/src/supermemory/resources/connections.py @@ -2,12 +2,13 @@ from __future__ import annotations +from typing import Dict, Union, Optional from typing_extensions import Literal import httpx -from ..types import connection_create_params -from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +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 @@ -18,37 +19,40 @@ 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, + 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, @@ -68,10 +72,11 @@ 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}", + body=maybe_transform({"metadata": metadata}, connection_create_params.ConnectionCreateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -79,7 +84,7 @@ def create( timeout=timeout, query=maybe_transform( { - "id": id, + "end_user_id": end_user_id, "redirect_url": redirect_url, }, connection_create_params.ConnectionCreateParams, @@ -88,7 +93,42 @@ def create( cast_to=ConnectionCreateResponse, ) - def retrieve( + 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ConnectionListResponse: + """ + 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, + query=maybe_transform({"end_user_id": end_user_id}, connection_list_params.ConnectionListParams), + ), + cast_to=ConnectionListResponse, + ) + + def get( self, connection_id: str, *, @@ -98,8 +138,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 +153,42 @@ 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, + 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, @@ -166,10 +208,11 @@ 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}", + body=await async_maybe_transform({"metadata": metadata}, connection_create_params.ConnectionCreateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -177,7 +220,7 @@ async def create( timeout=timeout, query=await async_maybe_transform( { - "id": id, + "end_user_id": end_user_id, "redirect_url": redirect_url, }, connection_create_params.ConnectionCreateParams, @@ -186,7 +229,44 @@ async def create( cast_to=ConnectionCreateResponse, ) - async def retrieve( + 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, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> ConnectionListResponse: + """ + 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, + query=await async_maybe_transform( + {"end_user_id": end_user_id}, connection_list_params.ConnectionListParams + ), + ), + cast_to=ConnectionListResponse, + ) + + async def get( self, connection_id: str, *, @@ -196,8 +276,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 +291,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 56% rename from src/supermemory/resources/memory.py rename to src/supermemory/resources/memories.py index e0368257..180f4cd6 100644 --- a/src/supermemory/resources/memory.py +++ b/src/supermemory/resources/memories.py @@ -2,14 +2,14 @@ from __future__ import annotations -from typing import Dict, Union +from typing import Dict, List, Union, Mapping, cast from typing_extensions import Literal import httpx -from ..types import memory_list_params, memory_create_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 ( @@ -19,58 +19,54 @@ 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 +from ..types.memory_update_response import MemoryUpdateResponse +from ..types.memory_upload_file_response import MemoryUploadFileResponse -__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 update( self, + id: str, *, content: str, - id: str | NotGiven = NOT_GIVEN, + container_tags: List[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: + ) -> MemoryUpdateResponse: """ - Add a new memory with content and metadata + Update a memory with any content type (text, url, file, etc.) and metadata Args: - content: Content of the memory - - metadata: Optional metadata for the memory - - user_id: Optional end user ID this memory belongs to - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -79,21 +75,22 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/add", + 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, - "id": id, + "container_tags": container_tags, "metadata": metadata, - "user_id": user_id, }, - memory_create_params.MemoryCreateParams, + memory_update_params.MemoryUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryCreateResponse, + cast_to=MemoryUpdateResponse, ) def list( @@ -134,7 +131,7 @@ def list( timeout: Override the client-level default timeout for this request, in seconds """ return self._get( - "/memories", + "/v3/memories", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -180,13 +177,54 @@ def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return self._delete( - f"/delete/{id}", + f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=MemoryDeleteResponse, ) + def add( + self, + *, + 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, + ) -> MemoryAddResponse: + """ + Add 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 + """ + 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=MemoryAddResponse, + ) + def get( self, id: str, @@ -213,58 +251,91 @@ 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 ), 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 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 update( self, + id: str, *, content: str, - id: str | NotGiven = NOT_GIVEN, + container_tags: List[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: + ) -> MemoryUpdateResponse: """ - Add a new memory with content and metadata + Update a memory with any content type (text, url, file, etc.) and metadata Args: - content: Content of the memory - - metadata: Optional metadata for the memory - - user_id: Optional end user ID this memory belongs to - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -273,21 +344,22 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/add", + 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, - "id": id, + "container_tags": container_tags, "metadata": metadata, - "user_id": user_id, }, - memory_create_params.MemoryCreateParams, + memory_update_params.MemoryUpdateParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryCreateResponse, + cast_to=MemoryUpdateResponse, ) async def list( @@ -328,7 +400,7 @@ async def list( timeout: Override the client-level default timeout for this request, in seconds """ return await self._get( - "/memories", + "/v3/memories", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -374,13 +446,54 @@ async def delete( if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") return await self._delete( - f"/delete/{id}", + f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=MemoryDeleteResponse, ) + async def add( + self, + *, + 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, + ) -> MemoryAddResponse: + """ + Add 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 + """ + 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=MemoryAddResponse, + ) + async def get( self, id: str, @@ -407,81 +520,144 @@ 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 ), 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 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.update = to_raw_response_wrapper( + memories.update, ) 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, + ) + self.upload_file = to_raw_response_wrapper( + memories.upload_file, ) -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.update = async_to_raw_response_wrapper( + memories.update, ) 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, + ) + self.upload_file = async_to_raw_response_wrapper( + memories.upload_file, ) -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.update = to_streamed_response_wrapper( + memories.update, ) 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, + ) + self.upload_file = to_streamed_response_wrapper( + memories.upload_file, ) -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.update = async_to_streamed_response_wrapper( + memories.update, ) 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, + ) + self.upload_file = async_to_streamed_response_wrapper( + memories.upload_file, ) diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py index 6c55ba34..4e293996 100644 --- a/src/supermemory/resources/search.py +++ b/src/supermemory/resources/search.py @@ -56,6 +56,8 @@ 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. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -65,29 +67,43 @@ def execute( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SearchExecuteResponse: """ - Search through documents with metadata filtering + Search memories 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. - user_id: End user ID this search is associated with + 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 @@ -97,25 +113,30 @@ def execute( 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, - "user_id": user_id, - }, - search_execute_params.SearchExecuteParams, - ), + return self._get( + "/v3/search", 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( + { + "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, ) @@ -153,6 +174,8 @@ 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. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -162,29 +185,43 @@ async def execute( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> SearchExecuteResponse: """ - Search through documents with metadata filtering + Search memories 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. - user_id: End user ID this search is associated with + 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 @@ -194,25 +231,30 @@ async def execute( 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, - "user_id": user_id, - }, - search_execute_params.SearchExecuteParams, - ), + return await self._get( + "/v3/search", 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( + { + "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, ) 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..2bb705ae 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -2,15 +2,23 @@ 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_create_params import MemoryCreateParams as MemoryCreateParams 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_create_response import MemoryCreateResponse as MemoryCreateResponse +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 .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/connection_create_params.py b/src/supermemory/types/connection_create_params.py index 4bb84cba..152cbce3 100644 --- a/src/supermemory/types/connection_create_params.py +++ b/src/supermemory/types/connection_create_params.py @@ -2,7 +2,8 @@ from __future__ import annotations -from typing_extensions import Required, Annotated, TypedDict +from typing import Dict, Union, Optional +from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -10,6 +11,8 @@ class ConnectionCreateParams(TypedDict, total=False): - id: Required[str] + 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 d27a6fab..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 @@ -8,6 +10,10 @@ class ConnectionCreateResponse(BaseModel): + id: str + + auth_link: str = FieldInfo(alias="authLink") + expires_in: str = FieldInfo(alias="expiresIn") - magic_link: str = FieldInfo(alias="magicLink") + redirects_to: Optional[str] = FieldInfo(alias="redirectsTo", default=None) 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_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/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 dc873287..00000000 --- a/src/supermemory/types/memory_create_params.py +++ /dev/null @@ -1,23 +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] - """Content of the memory""" - - id: str - - metadata: Dict[str, Union[str, float, bool]] - """Optional metadata for the memory""" - - user_id: Annotated[str, PropertyInfo(alias="userId")] - """Optional end user ID this memory belongs to""" diff --git a/src/supermemory/types/memory_get_response.py b/src/supermemory/types/memory_get_response.py index d915a18d..4d115240 100644 --- a/src/supermemory/types/memory_get_response.py +++ b/src/supermemory/types/memory_get_response.py @@ -1,27 +1,11 @@ # 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__ = ["MemoryGetResponse", "Doc"] - - -class Doc(BaseModel): - created_at: str = FieldInfo(alias="createdAt") - - updated_at: str = FieldInfo(alias="updatedAt") - - metadata: Optional[Dict[str, object]] = None - - summary: Optional[str] = None - - title: Optional[str] = None +__all__ = ["MemoryGetResponse"] class MemoryGetResponse(BaseModel): - doc: Doc + id: str - status: Optional[str] = None + status: str diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py index b5490f7f..5b325c78 100644 --- a/src/supermemory/types/memory_list_response.py +++ b/src/supermemory/types/memory_list_response.py @@ -1,6 +1,6 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, List, Optional +from typing import Dict, List, Union, Optional from datetime import datetime from typing_extensions import Literal @@ -13,33 +13,69 @@ class Memory(BaseModel): id: str - """Unique identifier of the memory""" + """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""" - metadata: Dict[str, object] - """Custom metadata associated with the memory""" + 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. - status: Optional[Literal["queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"]] = None - """Processing status of 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: str + 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 - """Source URL of the memory""" + """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. + """ - workflow_status: Optional[Literal["PENDING", "IN_PROGRESS", "COMPLETED", "FAILED"]] = FieldInfo( - alias="workflowStatus", default=None - ) - """Current workflow status""" + raw: None = None + """Raw content of the memory""" class Pagination(BaseModel): 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/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/src/supermemory/types/search_execute_params.py b/src/supermemory/types/search_execute_params.py index 7ad50125..bc3b4756 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,17 +45,36 @@ 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. + """ + + 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""" + """End user ID this search is associated with. + + NOTE: This also acts as a filter for the search. + """ class FiltersUnionMember0(TypedDict, total=False): 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..aee15f42 --- /dev/null +++ b/tests/api_resources/test_connections.py @@ -0,0 +1,272 @@ +# 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( + 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"]) + + @pytest.mark.skip() + @parametrize + def test_raw_response_create(self, client: Supermemory) -> None: + response = client.connections.with_raw_response.create( + provider="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( + provider="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_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: + 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( + 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"]) + + @pytest.mark.skip() + @parametrize + async def test_raw_response_create(self, async_client: AsyncSupermemory) -> None: + response = await async_client.connections.with_raw_response.create( + provider="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( + provider="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_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: + 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 51% rename from tests/api_resources/test_memory.py rename to tests/api_resources/test_memories.py index 871f95ce..8e7130f2 100644 --- a/tests/api_resources/test_memory.py +++ b/tests/api_resources/test_memories.py @@ -10,32 +10,36 @@ from supermemory import Supermemory, AsyncSupermemory from tests.utils import assert_matches_type from supermemory.types import ( + MemoryAddResponse, MemoryGetResponse, MemoryListResponse, - MemoryCreateResponse, MemoryDeleteResponse, + MemoryUpdateResponse, + MemoryUploadFileResponse, ) 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( + 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(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryUpdateResponse, 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...", + 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", @@ -44,46 +48,56 @@ def test_method_create_with_all_params(self, client: Supermemory) -> None: "readingTime": 5, "isPublic": True, }, - user_id="user_123", ) - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_raw_response_create(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.create( + 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(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryUpdateResponse, memory, path=["response"]) @pytest.mark.skip() @parametrize - def test_streaming_response_create(self, client: Supermemory) -> None: - with client.memory.with_streaming_response.create( + 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(MemoryCreateResponse, memory, path=["response"]) + 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: - memory = client.memory.list() + 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.memory.list( + memory = client.memories.list( filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', limit="10", order="desc", @@ -95,7 +109,7 @@ def test_method_list_with_all_params(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_raw_response_list(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.list() + response = client.memories.with_raw_response.list() assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -105,7 +119,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.memory.with_streaming_response.list() 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" @@ -117,7 +131,7 @@ def test_streaming_response_list(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_method_delete(self, client: Supermemory) -> None: - memory = client.memory.delete( + memory = client.memories.delete( "id", ) assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) @@ -125,7 +139,7 @@ def test_method_delete(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_raw_response_delete(self, client: Supermemory) -> None: - response = client.memory.with_raw_response.delete( + response = client.memories.with_raw_response.delete( "id", ) @@ -137,7 +151,7 @@ def test_raw_response_delete(self, client: Supermemory) -> None: @pytest.mark.skip() @parametrize def test_streaming_response_delete(self, client: Supermemory) -> None: - with client.memory.with_streaming_response.delete( + with client.memories.with_streaming_response.delete( "id", ) as response: assert not response.is_closed @@ -152,14 +166,65 @@ def test_streaming_response_delete(self, client: Supermemory) -> None: @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( + 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(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=["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(MemoryAddResponse, memory, path=["response"]) + + @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: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(MemoryAddResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + @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 +232,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 +244,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,28 +259,64 @@ 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( "", ) + @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 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( + 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(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryUpdateResponse, 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...", + 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", @@ -224,46 +325,56 @@ async def test_method_create_with_all_params(self, async_client: AsyncSupermemor "readingTime": 5, "isPublic": True, }, - user_id="user_123", ) - assert_matches_type(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryUpdateResponse, 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( + 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(MemoryCreateResponse, memory, path=["response"]) + assert_matches_type(MemoryUpdateResponse, 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( + 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(MemoryCreateResponse, memory, path=["response"]) + 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: - memory = await async_client.memory.list() + 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.memory.list( + memory = await async_client.memories.list( filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', limit="10", order="desc", @@ -275,7 +386,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncSupermemory) @pytest.mark.skip() @parametrize async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memory.with_raw_response.list() + 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" @@ -285,7 +396,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.memory.with_streaming_response.list() 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" @@ -297,7 +408,7 @@ async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memory.delete( + memory = await async_client.memories.delete( "id", ) assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) @@ -305,7 +416,7 @@ async def test_method_delete(self, async_client: AsyncSupermemory) -> None: @pytest.mark.skip() @parametrize async def test_raw_response_delete(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memory.with_raw_response.delete( + response = await async_client.memories.with_raw_response.delete( "id", ) @@ -317,7 +428,7 @@ async def test_raw_response_delete(self, async_client: AsyncSupermemory) -> None @pytest.mark.skip() @parametrize async def test_streaming_response_delete(self, async_client: AsyncSupermemory) -> None: - async with async_client.memory.with_streaming_response.delete( + async with async_client.memories.with_streaming_response.delete( "id", ) as response: assert not response.is_closed @@ -332,14 +443,65 @@ async def test_streaming_response_delete(self, async_client: AsyncSupermemory) - @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( + 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(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=["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(MemoryAddResponse, memory, path=["response"]) + + @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: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(MemoryAddResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + @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 +509,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 +521,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 +536,40 @@ 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( "", ) + + @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 diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py index 5bf10690..c0557ff5 100644 --- a/tests/api_resources/test_search.py +++ b/tests/api_resources/test_search.py @@ -54,6 +54,8 @@ 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", ) assert_matches_type(SearchExecuteResponse, search, path=["response"]) @@ -125,6 +127,8 @@ 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", ) assert_matches_type(SearchExecuteResponse, search, path=["response"]) 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 b625c8c7..3636a001 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -33,7 +33,7 @@ BaseClient, make_request_options, ) -from supermemory.types.memory_create_params import MemoryCreateParams +from supermemory.types.memory_add_params import MemoryAddParams from .utils import update_env @@ -339,7 +339,7 @@ def test_default_headers_option(self) -> None: 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 + assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(SupermemoryError): with update_env(**{"SUPERMEMORY_API_KEY": Omit()}): @@ -724,16 +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.post("/add").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.post( - "/add", + "/v3/memories", 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..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -745,16 +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.post("/add").mock(return_value=httpx.Response(500)) + respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): self.client.post( - "/add", + "/v3/memories", 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..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -787,9 +785,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.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memory.with_raw_response.create( + response = client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts..." ) @@ -813,9 +811,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.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memory.with_raw_response.create( + response = client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", extra_headers={"x-stainless-retry-count": Omit()}, ) @@ -839,9 +837,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.post("/v3/memories").mock(side_effect=retry_handler) - response = client.memory.with_raw_response.create( + response = client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts...", extra_headers={"x-stainless-retry-count": "42"}, ) @@ -1131,7 +1129,7 @@ def test_default_headers_option(self) -> None: 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 + assert request.headers.get("Authorization") == f"Bearer {api_key}" with pytest.raises(SupermemoryError): with update_env(**{"SUPERMEMORY_API_KEY": Omit()}): @@ -1520,16 +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.post("/add").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.post( - "/add", + "/v3/memories", 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..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -1541,16 +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.post("/add").mock(return_value=httpx.Response(500)) + respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): await self.client.post( - "/add", + "/v3/memories", 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..."), MemoryAddParams ), ), cast_to=httpx.Response, @@ -1584,9 +1580,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.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memory.with_raw_response.create( + response = await client.memories.with_raw_response.add( content="This is a detailed article about machine learning concepts..." ) @@ -1611,9 +1607,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.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memory.with_raw_response.create( + 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()}, ) @@ -1638,9 +1634,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.post("/v3/memories").mock(side_effect=retry_handler) - response = await client.memory.with_raw_response.create( + 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"}, ) 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)