diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71338782..43988b20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,10 @@ on: - 'integrated/**' - 'stl-preview-head/**' - 'stl-preview-base/**' + pull_request: + branches-ignore: + - 'stl-preview-head/**' + - 'stl-preview-base/**' jobs: lint: diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2f23d818..4ae7e4a0 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.0.0-alpha.1" + ".": "3.0.0-alpha.2" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 4be06c0d..8f056938 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-e6735b03c258b382c527550bb78042bdc3aad32a5cf564785dcb9f3fb13a2862.yml -openapi_spec_hash: 8168fb51314d986893554e1cc935ca7d +configured_endpoints: 8 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/supermemory--inc%2Fsupermemory-4c45e387cbdc7c80d75cdb8eb924cf92a3a48a0c10060fda917b83a7e454aef5.yml +openapi_spec_hash: c859ac2e3429ad3663337b99c722f317 config_hash: 8477e3ee6fd596ab6ac911d052e4de79 diff --git a/CHANGELOG.md b/CHANGELOG.md index ecf6170f..68ff2aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # Changelog +## 3.0.0-alpha.2 (2025-06-24) + +Full Changelog: [v3.0.0-alpha.1...v3.0.0-alpha.2](https://github.com/supermemoryai/python-sdk/compare/v3.0.0-alpha.1...v3.0.0-alpha.2) + +### Features + +* **api:** api update ([75c22e3](https://github.com/supermemoryai/python-sdk/commit/75c22e3f2db8eda857ca184f5bd531ff736a1c8c)) +* **api:** api update ([fc586f7](https://github.com/supermemoryai/python-sdk/commit/fc586f729ac602946e95de2e61b6d27a8c3b1167)) +* **api:** api update ([5aefc85](https://github.com/supermemoryai/python-sdk/commit/5aefc85540c70a195c2c59a8e9cdd529ff124a3b)) +* **api:** api update ([200e97a](https://github.com/supermemoryai/python-sdk/commit/200e97acf9f2be46a3ffef069912da9e4d05a951)) +* **api:** api update ([2d9b26c](https://github.com/supermemoryai/python-sdk/commit/2d9b26ce58cf533ffbff6d7a002c8504752c1e6a)) +* **api:** api update ([c7e0c2c](https://github.com/supermemoryai/python-sdk/commit/c7e0c2ccae941136705f2328fe536870789cf52a)) +* **api:** api update ([486888d](https://github.com/supermemoryai/python-sdk/commit/486888da89d570779e132f3b810c7d1a4f59082a)) +* **api:** api update ([378085f](https://github.com/supermemoryai/python-sdk/commit/378085f0090ebf25bbc46d0e3947737457ae3a30)) +* **api:** api update ([7b7711f](https://github.com/supermemoryai/python-sdk/commit/7b7711f282eebbaf4edb462b1dc2ed5dfb1bc0fc)) +* **api:** api update ([c194b2e](https://github.com/supermemoryai/python-sdk/commit/c194b2ecbe42ebe5997aab4bc96fb7120a5f6b9c)) +* **api:** api update ([d517cbc](https://github.com/supermemoryai/python-sdk/commit/d517cbce5d74b7cd92d8921d16ba1bb025848549)) +* **api:** api update ([fa9f582](https://github.com/supermemoryai/python-sdk/commit/fa9f582226ed9dee9ea8196b78b9312938093465)) +* **api:** api update ([d1b2a97](https://github.com/supermemoryai/python-sdk/commit/d1b2a9719deb080c4b64ae97893ba139ecaebf68)) +* **api:** api update ([02180e2](https://github.com/supermemoryai/python-sdk/commit/02180e296f97a30626b183ba533f45879981cc62)) +* **api:** api update ([51586e6](https://github.com/supermemoryai/python-sdk/commit/51586e68f9ec7b04b7aba149142f6e550113c0b9)) +* **api:** api update ([cd4782d](https://github.com/supermemoryai/python-sdk/commit/cd4782de2b4f05196e6b3f37768de900a6a9e81d)) +* **api:** api update ([f58d93d](https://github.com/supermemoryai/python-sdk/commit/f58d93dc249798bdf62c00335c6b9bfdf8430795)) +* **api:** api update ([e95b77e](https://github.com/supermemoryai/python-sdk/commit/e95b77ee719a76f0eb22c56f07e90f05712de7cd)) +* **api:** api update ([beffc5e](https://github.com/supermemoryai/python-sdk/commit/beffc5e09b5aee9c1517453b2f5574881ecf5dd7)) +* **api:** api update ([173a337](https://github.com/supermemoryai/python-sdk/commit/173a337626cbd75d951d10a04e91261ed3a2a384)) +* **api:** api update ([02e8b00](https://github.com/supermemoryai/python-sdk/commit/02e8b0072cb84a52124f61f5d8f64d69809c7833)) +* **api:** api update ([1788d47](https://github.com/supermemoryai/python-sdk/commit/1788d476d76fd786ed495ee5f8048c301fae0799)) +* **api:** api update ([5bc5a86](https://github.com/supermemoryai/python-sdk/commit/5bc5a8611c00f3c77c1e3787f578c91577ddfbb0)) +* **api:** api update ([0c627bd](https://github.com/supermemoryai/python-sdk/commit/0c627bde6c7f6ef8d55e94993a612f34c75c7f80)) +* **client:** add follow_redirects request option ([abd637d](https://github.com/supermemoryai/python-sdk/commit/abd637db1e3642c0dcc609bf4cac3d0a2bdad061)) +* **client:** add support for aiohttp ([d376719](https://github.com/supermemoryai/python-sdk/commit/d37671920ac604d643c0d9edaaee08aaaea2d881)) + + +### Bug Fixes + +* **client:** correctly parse binary response | stream ([b236e85](https://github.com/supermemoryai/python-sdk/commit/b236e8552e9393048b3541c2c41d969fd5a88ac0)) +* **tests:** fix: tests which call HTTP endpoints directly with the example parameters ([f9bf3c1](https://github.com/supermemoryai/python-sdk/commit/f9bf3c135c6a6236c8ef0ee5d538843021448b9d)) + + +### Chores + +* **ci:** enable for pull requests ([ec1b12b](https://github.com/supermemoryai/python-sdk/commit/ec1b12b9447ff582d80a0dedf75dc5c924aee6e4)) +* **docs:** grammar improvements ([1f9f018](https://github.com/supermemoryai/python-sdk/commit/1f9f01876a2754521ab8cc75c92634b05221f6c5)) +* **docs:** remove reference to rye shell ([23840c8](https://github.com/supermemoryai/python-sdk/commit/23840c8b8a67cb3b60e94c1eb74994d28b3508f1)) +* **internal:** codegen related update ([2b10aa8](https://github.com/supermemoryai/python-sdk/commit/2b10aa88d82e044d1a7df2bdd266209f4c130c8d)) +* **internal:** codegen related update ([c4039a7](https://github.com/supermemoryai/python-sdk/commit/c4039a7d5124d2c6719508830bf8067ebbb4fd58)) +* **internal:** update conftest.py ([355810b](https://github.com/supermemoryai/python-sdk/commit/355810b458222ddf405ec130cf43887cf7b2138d)) +* **readme:** update badges ([9415b50](https://github.com/supermemoryai/python-sdk/commit/9415b50222cea2e827a8e9c13525ad8e19df9ff2)) +* **tests:** add tests for httpx client instantiation & proxies ([a462f22](https://github.com/supermemoryai/python-sdk/commit/a462f2240dac23bf780f540ba39da3febbc561e7)) +* **tests:** run tests in parallel ([79f6359](https://github.com/supermemoryai/python-sdk/commit/79f6359beb267f85e273a5a3017283d9e231e78a)) +* **tests:** skip some failing tests on the latest python versions ([394e639](https://github.com/supermemoryai/python-sdk/commit/394e639fb904cd4c27b299f5960fd2f02f159b10)) + + +### Documentation + +* **client:** fix httpx.Timeout documentation reference ([be3ccbd](https://github.com/supermemoryai/python-sdk/commit/be3ccbdebe4974592c670cb43ed572ced78e60a3)) + ## 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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9426a1fa..0f988c37 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,8 +17,7 @@ $ rye sync --all-features You can then run scripts using `rye run python script.py` or by activating the virtual environment: ```sh -$ rye shell -# or manually activate - https://docs.python.org/3/library/venv.html#how-venvs-work +# Activate the virtual environment - https://docs.python.org/3/library/venv.html#how-venvs-work $ source .venv/bin/activate # now you can omit the `rye run` prefix diff --git a/README.md b/README.md index 55246cf4..8557c60b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Supermemory Python API library -[![PyPI version](https://img.shields.io/pypi/v/supermemory.svg)](https://pypi.org/project/supermemory/) +[![PyPI version]()](https://pypi.org/project/supermemory/) The Supermemory Python library provides convenient access to the Supermemory REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, @@ -31,10 +31,10 @@ client = Supermemory( api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted ) -response = client.search.execute( - q="documents related to python", +response = client.memories.add( + content="This is a detailed article about machine learning concepts...", ) -print(response.results) +print(response.id) ``` While you can provide an `api_key` keyword argument, @@ -57,10 +57,10 @@ client = AsyncSupermemory( async def main() -> None: - response = await client.search.execute( - q="documents related to python", + response = await client.memories.add( + content="This is a detailed article about machine learning concepts...", ) - print(response.results) + print(response.id) asyncio.run(main()) @@ -68,31 +68,48 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. -## Using types - -Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: +### With aiohttp -- Serializing back into JSON, `model.to_json()` -- Converting to a dictionary, `model.to_dict()` +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. -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`. +You can enable this by installing `aiohttp`: -## File uploads +```sh +# install from PyPI +pip install --pre supermemory[aiohttp] +``` -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)`. +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python -from pathlib import Path -from supermemory import Supermemory +import os +import asyncio +from supermemory import DefaultAioHttpClient +from supermemory import AsyncSupermemory -client = Supermemory() -client.memories.upload_file( - file=Path("/path/to/file"), -) +async def main() -> None: + async with AsyncSupermemory( + api_key=os.environ.get("SUPERMEMORY_API_KEY"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + response = await client.memories.add( + content="This is a detailed article about machine learning concepts...", + ) + print(response.id) + + +asyncio.run(main()) ``` -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. +## Using types + +Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typing.html#typing.TypedDict). Responses are [Pydantic models](https://docs.pydantic.dev) which also provide helper methods for things like: + +- Serializing back into JSON, `model.to_json()` +- Converting to a dictionary, `model.to_dict()` + +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`. ## Handling errors @@ -163,7 +180,7 @@ client.with_options(max_retries=5).memories.add( ### Timeouts By default requests time out after 1 minute. You can configure this with a `timeout` option, -which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/#fine-tuning-the-configuration) object: +which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python from supermemory import Supermemory diff --git a/SECURITY.md b/SECURITY.md index 80798832..858d41ff 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,11 +16,11 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Supermemory please follow the respective company's security reporting guidelines. +or products provided by Supermemory, please follow the respective company's security reporting guidelines. ### Supermemory Terms and Policies -Please contact dhravya@supermemory.com for any questions or concerns regarding security of our services. +Please contact dhravya@supermemory.com for any questions or concerns regarding the security of our services. --- diff --git a/api.md b/api.md index 66639691..77c6c399 100644 --- a/api.md +++ b/api.md @@ -3,36 +3,15 @@ Types: ```python -from supermemory.types import ( - MemoryUpdateResponse, - MemoryListResponse, - MemoryDeleteResponse, - MemoryAddResponse, - MemoryGetResponse, - MemoryUploadFileResponse, -) +from supermemory.types import MemoryUpdateResponse, MemoryAddResponse, MemoryGetResponse ``` Methods: - client.memories.update(id, \*\*params) -> MemoryUpdateResponse -- client.memories.list(\*\*params) -> MemoryListResponse -- client.memories.delete(id) -> MemoryDeleteResponse +- client.memories.delete(id) -> None - client.memories.add(\*\*params) -> MemoryAddResponse - client.memories.get(id) -> MemoryGetResponse -- client.memories.upload_file(\*\*params) -> MemoryUploadFileResponse - -# Search - -Types: - -```python -from supermemory.types import SearchExecuteResponse -``` - -Methods: - -- client.search.execute(\*\*params) -> SearchExecuteResponse # Settings @@ -52,15 +31,10 @@ Methods: Types: ```python -from supermemory.types import ( - ConnectionCreateResponse, - ConnectionListResponse, - ConnectionGetResponse, -) +from supermemory.types import ConnectionCreateResponse, ConnectionGetResponse ``` Methods: - 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 9ca24d46..51b28cf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "supermemory" -version = "3.0.0-alpha.1" +version = "3.0.0-alpha.2" description = "The official Python library for the supermemory API" dynamic = ["readme"] license = "Apache-2.0" @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/supermemoryai/python-sdk" Repository = "https://github.com/supermemoryai/python-sdk" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true @@ -54,6 +56,7 @@ dev-dependencies = [ "importlib-metadata>=6.7.0", "rich>=13.7.1", "nest_asyncio==1.6.0", + "pytest-xdist>=3.6.1", ] [tool.rye.scripts] @@ -125,7 +128,7 @@ replacement = '[\1](https://github.com/supermemoryai/python-sdk/tree/main/\g<2>) [tool.pytest.ini_options] testpaths = ["tests"] -addopts = "--tb=short" +addopts = "--tb=short -n auto" xfail_strict = true asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" diff --git a/requirements-dev.lock b/requirements-dev.lock index efa05b0d..c63e536d 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -10,6 +10,13 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via httpx-aiohttp + # via supermemory +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 @@ -17,6 +24,10 @@ anyio==4.4.0 # via supermemory argcomplete==3.1.2 # via nox +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -30,18 +41,27 @@ distro==1.8.0 exceptiongroup==1.2.2 # via anyio # via pytest +execnet==2.1.1 + # via pytest-xdist filelock==3.12.4 # via virtualenv +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 + # via httpx-aiohttp # via respx # via supermemory +httpx-aiohttp==0.1.6 + # via supermemory idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -49,6 +69,9 @@ markdown-it-py==3.0.0 # via rich mdurl==0.1.2 # via markdown-it-py +multidict==6.4.4 + # via aiohttp + # via yarl mypy==1.14.1 mypy-extensions==1.0.0 # via mypy @@ -63,6 +86,9 @@ platformdirs==3.11.0 # via virtualenv pluggy==1.5.0 # via pytest +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via supermemory pydantic-core==2.27.1 @@ -72,7 +98,9 @@ pygments==2.18.0 pyright==1.1.399 pytest==8.3.3 # via pytest-asyncio + # via pytest-xdist pytest-asyncio==0.24.0 +pytest-xdist==3.7.0 python-dateutil==2.8.2 # via time-machine pytz==2023.3.post1 @@ -93,6 +121,7 @@ tomli==2.0.2 # via pytest typing-extensions==4.12.2 # via anyio + # via multidict # via mypy # via pydantic # via pydantic-core @@ -100,5 +129,7 @@ typing-extensions==4.12.2 # via supermemory virtualenv==20.24.5 # via nox +yarl==1.20.0 + # via aiohttp zipp==3.17.0 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index c3540f77..c54f68b1 100644 --- a/requirements.lock +++ b/requirements.lock @@ -10,11 +10,22 @@ # universal: false -e file:. +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.12.8 + # via httpx-aiohttp + # via supermemory +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via httpx # via supermemory +async-timeout==5.0.1 + # via aiohttp +attrs==25.3.0 + # via aiohttp certifi==2023.7.22 # via httpcore # via httpx @@ -22,15 +33,28 @@ distro==1.8.0 # via supermemory exceptiongroup==1.2.2 # via anyio +frozenlist==1.6.2 + # via aiohttp + # via aiosignal h11==0.14.0 # via httpcore httpcore==1.0.2 # via httpx httpx==0.28.1 + # via httpx-aiohttp + # via supermemory +httpx-aiohttp==0.1.6 # via supermemory idna==3.4 # via anyio # via httpx + # via yarl +multidict==6.4.4 + # via aiohttp + # via yarl +propcache==0.3.1 + # via aiohttp + # via yarl pydantic==2.10.3 # via supermemory pydantic-core==2.27.1 @@ -40,6 +64,9 @@ sniffio==1.3.0 # via supermemory typing-extensions==4.12.2 # via anyio + # via multidict # via pydantic # via pydantic-core # via supermemory +yarl==1.20.0 + # via aiohttp diff --git a/src/supermemory/__init__.py b/src/supermemory/__init__.py index 3cc510e4..bb87a0cf 100644 --- a/src/supermemory/__init__.py +++ b/src/supermemory/__init__.py @@ -36,7 +36,7 @@ UnprocessableEntityError, APIResponseValidationError, ) -from ._base_client import DefaultHttpxClient, DefaultAsyncHttpxClient +from ._base_client import DefaultHttpxClient, DefaultAioHttpClient, DefaultAsyncHttpxClient from ._utils._logs import setup_logging as _setup_logging __all__ = [ @@ -78,6 +78,7 @@ "DEFAULT_CONNECTION_LIMITS", "DefaultHttpxClient", "DefaultAsyncHttpxClient", + "DefaultAioHttpClient", ] if not _t.TYPE_CHECKING: diff --git a/src/supermemory/_base_client.py b/src/supermemory/_base_client.py index ca79f9a6..9a46a403 100644 --- a/src/supermemory/_base_client.py +++ b/src/supermemory/_base_client.py @@ -960,6 +960,9 @@ def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1068,7 +1071,14 @@ def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, APIResponse): raise TypeError(f"API Response types must subclass {APIResponse}; Received {origin}") @@ -1279,6 +1289,24 @@ def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) +try: + import httpx_aiohttp +except ImportError: + + class _DefaultAioHttpClient(httpx.AsyncClient): + def __init__(self, **_kwargs: Any) -> None: + raise RuntimeError("To use the aiohttp client you must have installed the package with the `aiohttp` extra") +else: + + class _DefaultAioHttpClient(httpx_aiohttp.HttpxAiohttpClient): # type: ignore + def __init__(self, **kwargs: Any) -> None: + kwargs.setdefault("timeout", DEFAULT_TIMEOUT) + kwargs.setdefault("limits", DEFAULT_CONNECTION_LIMITS) + kwargs.setdefault("follow_redirects", True) + + super().__init__(**kwargs) + + if TYPE_CHECKING: DefaultAsyncHttpxClient = httpx.AsyncClient """An alias to `httpx.AsyncClient` that provides the same defaults that this SDK @@ -1287,8 +1315,12 @@ def __init__(self, **kwargs: Any) -> None: This is useful because overriding the `http_client` with your own instance of `httpx.AsyncClient` will result in httpx's defaults being used, not ours. """ + + DefaultAioHttpClient = httpx.AsyncClient + """An alias to `httpx.AsyncClient` that changes the default HTTP transport to `aiohttp`.""" else: DefaultAsyncHttpxClient = _DefaultAsyncHttpxClient + DefaultAioHttpClient = _DefaultAioHttpClient class AsyncHttpxClientWrapper(DefaultAsyncHttpxClient): @@ -1460,6 +1492,9 @@ async def request( if self.custom_auth is not None: kwargs["auth"] = self.custom_auth + if options.follow_redirects is not None: + kwargs["follow_redirects"] = options.follow_redirects + log.debug("Sending HTTP Request: %s %s", request.method, request.url) response = None @@ -1568,7 +1603,14 @@ async def _process_response( ) -> ResponseT: origin = get_origin(cast_to) or cast_to - if inspect.isclass(origin) and issubclass(origin, BaseAPIResponse): + if ( + inspect.isclass(origin) + and issubclass(origin, BaseAPIResponse) + # we only want to actually return the custom BaseAPIResponse class if we're + # returning the raw response, or if we're not streaming SSE, as if we're streaming + # SSE then `cast_to` doesn't actively reflect the type we need to parse into + and (not stream or bool(response.request.headers.get(RAW_RESPONSE_HEADER))) + ): if not issubclass(origin, AsyncAPIResponse): raise TypeError(f"API Response types must subclass {AsyncAPIResponse}; Received {origin}") diff --git a/src/supermemory/_client.py b/src/supermemory/_client.py index e2e99f0b..0a336bf6 100644 --- a/src/supermemory/_client.py +++ b/src/supermemory/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import search, memories, settings, connections +from .resources import memories, settings, connections from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, SupermemoryError from ._base_client import ( @@ -44,7 +44,6 @@ class Supermemory(SyncAPIClient): memories: memories.MemoriesResource - search: search.SearchResource settings: settings.SettingsResource connections: connections.ConnectionsResource with_raw_response: SupermemoryWithRawResponse @@ -105,7 +104,6 @@ def __init__( ) self.memories = memories.MemoriesResource(self) - self.search = search.SearchResource(self) self.settings = settings.SettingsResource(self) self.connections = connections.ConnectionsResource(self) self.with_raw_response = SupermemoryWithRawResponse(self) @@ -218,7 +216,6 @@ def _make_status_error( class AsyncSupermemory(AsyncAPIClient): memories: memories.AsyncMemoriesResource - search: search.AsyncSearchResource settings: settings.AsyncSettingsResource connections: connections.AsyncConnectionsResource with_raw_response: AsyncSupermemoryWithRawResponse @@ -279,7 +276,6 @@ def __init__( ) self.memories = memories.AsyncMemoriesResource(self) - self.search = search.AsyncSearchResource(self) self.settings = settings.AsyncSettingsResource(self) self.connections = connections.AsyncConnectionsResource(self) self.with_raw_response = AsyncSupermemoryWithRawResponse(self) @@ -393,7 +389,6 @@ def _make_status_error( class SupermemoryWithRawResponse: def __init__(self, client: Supermemory) -> None: self.memories = memories.MemoriesResourceWithRawResponse(client.memories) - self.search = search.SearchResourceWithRawResponse(client.search) self.settings = settings.SettingsResourceWithRawResponse(client.settings) self.connections = connections.ConnectionsResourceWithRawResponse(client.connections) @@ -401,7 +396,6 @@ def __init__(self, client: Supermemory) -> None: class AsyncSupermemoryWithRawResponse: def __init__(self, client: AsyncSupermemory) -> None: self.memories = memories.AsyncMemoriesResourceWithRawResponse(client.memories) - self.search = search.AsyncSearchResourceWithRawResponse(client.search) self.settings = settings.AsyncSettingsResourceWithRawResponse(client.settings) self.connections = connections.AsyncConnectionsResourceWithRawResponse(client.connections) @@ -409,7 +403,6 @@ def __init__(self, client: AsyncSupermemory) -> None: class SupermemoryWithStreamedResponse: def __init__(self, client: Supermemory) -> None: self.memories = memories.MemoriesResourceWithStreamingResponse(client.memories) - self.search = search.SearchResourceWithStreamingResponse(client.search) self.settings = settings.SettingsResourceWithStreamingResponse(client.settings) self.connections = connections.ConnectionsResourceWithStreamingResponse(client.connections) @@ -417,7 +410,6 @@ def __init__(self, client: Supermemory) -> None: class AsyncSupermemoryWithStreamedResponse: def __init__(self, client: AsyncSupermemory) -> None: self.memories = memories.AsyncMemoriesResourceWithStreamingResponse(client.memories) - self.search = search.AsyncSearchResourceWithStreamingResponse(client.search) self.settings = settings.AsyncSettingsResourceWithStreamingResponse(client.settings) self.connections = connections.AsyncConnectionsResourceWithStreamingResponse(client.connections) diff --git a/src/supermemory/_files.py b/src/supermemory/_files.py index 0dcf63d3..715cc207 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. See https://github.com/supermemoryai/python-sdk/tree/main#file-uploads" + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." ) from None diff --git a/src/supermemory/_models.py b/src/supermemory/_models.py index 798956f1..4f214980 100644 --- a/src/supermemory/_models.py +++ b/src/supermemory/_models.py @@ -737,6 +737,7 @@ class FinalRequestOptionsInput(TypedDict, total=False): idempotency_key: str json_data: Body extra_json: AnyMapping + follow_redirects: bool @final @@ -750,6 +751,7 @@ class FinalRequestOptions(pydantic.BaseModel): files: Union[HttpxRequestFiles, None] = None idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() + follow_redirects: Union[bool, None] = None # It should be noted that we cannot use `json` here as that would override # a BaseModel method in an incompatible fashion. diff --git a/src/supermemory/_types.py b/src/supermemory/_types.py index 22b4e97c..8491d513 100644 --- a/src/supermemory/_types.py +++ b/src/supermemory/_types.py @@ -100,6 +100,7 @@ class RequestOptions(TypedDict, total=False): params: Query extra_json: AnyMapping idempotency_key: str + follow_redirects: bool # Sentinel class used until PEP 0661 is accepted @@ -215,3 +216,4 @@ class _GenericAlias(Protocol): class HttpxSendArgs(TypedDict, total=False): auth: httpx.Auth + follow_redirects: bool diff --git a/src/supermemory/_version.py b/src/supermemory/_version.py index c8cc1993..dfc97c46 100644 --- a/src/supermemory/_version.py +++ b/src/supermemory/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "supermemory" -__version__ = "3.0.0-alpha.1" # x-release-please-version +__version__ = "3.0.0-alpha.2" # x-release-please-version diff --git a/src/supermemory/resources/__init__.py b/src/supermemory/resources/__init__.py index 275ecfbe..5a1fb723 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 .search import ( - SearchResource, - AsyncSearchResource, - SearchResourceWithRawResponse, - AsyncSearchResourceWithRawResponse, - SearchResourceWithStreamingResponse, - AsyncSearchResourceWithStreamingResponse, -) from .memories import ( MemoriesResource, AsyncMemoriesResource, @@ -40,12 +32,6 @@ "AsyncMemoriesResourceWithRawResponse", "MemoriesResourceWithStreamingResponse", "AsyncMemoriesResourceWithStreamingResponse", - "SearchResource", - "AsyncSearchResource", - "SearchResourceWithRawResponse", - "AsyncSearchResourceWithRawResponse", - "SearchResourceWithStreamingResponse", - "AsyncSearchResourceWithStreamingResponse", "SettingsResource", "AsyncSettingsResource", "SettingsResourceWithRawResponse", diff --git a/src/supermemory/resources/connections.py b/src/supermemory/resources/connections.py index dfc95e77..f431a728 100644 --- a/src/supermemory/resources/connections.py +++ b/src/supermemory/resources/connections.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import Dict, Union, Optional +from typing import Dict, List, Union, Optional from typing_extensions import Literal import httpx -from ..types import connection_list_params, connection_create_params +from ..types import 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 @@ -20,7 +20,6 @@ ) 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__ = ["ConnectionsResource", "AsyncConnectionsResource"] @@ -50,9 +49,10 @@ def create( self, provider: Literal["notion", "google-drive", "onedrive"], *, - end_user_id: str | NotGiven = NOT_GIVEN, - redirect_url: str | NotGiven = NOT_GIVEN, + container_tags: List[str] | NotGiven = NOT_GIVEN, + document_limit: int | NotGiven = NOT_GIVEN, metadata: Optional[Dict[str, Union[str, float, bool]]] | NotGiven = NOT_GIVEN, + redirect_url: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -76,56 +76,19 @@ def create( raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") return self._post( f"/v3/connections/{provider}", - body=maybe_transform({"metadata": metadata}, connection_create_params.ConnectionCreateParams), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "end_user_id": end_user_id, - "redirect_url": redirect_url, - }, - connection_create_params.ConnectionCreateParams, - ), + body=maybe_transform( + { + "container_tags": container_tags, + "document_limit": document_limit, + "metadata": metadata, + "redirect_url": redirect_url, + }, + connection_create_params.ConnectionCreateParams, ), - cast_to=ConnectionCreateResponse, - ) - - 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), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ConnectionListResponse, + cast_to=ConnectionCreateResponse, ) def get( @@ -140,7 +103,7 @@ def get( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ConnectionGetResponse: """ - Get connection details + Get connection details with id Args: extra_headers: Send extra headers @@ -186,9 +149,10 @@ async def create( self, provider: Literal["notion", "google-drive", "onedrive"], *, - end_user_id: str | NotGiven = NOT_GIVEN, - redirect_url: str | NotGiven = NOT_GIVEN, + container_tags: List[str] | NotGiven = NOT_GIVEN, + document_limit: int | NotGiven = NOT_GIVEN, metadata: Optional[Dict[str, Union[str, float, bool]]] | NotGiven = NOT_GIVEN, + redirect_url: str | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -212,58 +176,19 @@ async def create( raise ValueError(f"Expected a non-empty value for `provider` but received {provider!r}") return await self._post( f"/v3/connections/{provider}", - body=await async_maybe_transform({"metadata": metadata}, connection_create_params.ConnectionCreateParams), - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "end_user_id": end_user_id, - "redirect_url": redirect_url, - }, - connection_create_params.ConnectionCreateParams, - ), + body=await async_maybe_transform( + { + "container_tags": container_tags, + "document_limit": document_limit, + "metadata": metadata, + "redirect_url": redirect_url, + }, + connection_create_params.ConnectionCreateParams, ), - cast_to=ConnectionCreateResponse, - ) - - 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 - ), + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ConnectionListResponse, + cast_to=ConnectionCreateResponse, ) async def get( @@ -278,7 +203,7 @@ async def get( timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, ) -> ConnectionGetResponse: """ - Get connection details + Get connection details with id Args: extra_headers: Send extra headers @@ -307,9 +232,6 @@ def __init__(self, connections: ConnectionsResource) -> None: self.create = to_raw_response_wrapper( connections.create, ) - self.list = to_raw_response_wrapper( - connections.list, - ) self.get = to_raw_response_wrapper( connections.get, ) @@ -322,9 +244,6 @@ def __init__(self, connections: AsyncConnectionsResource) -> None: self.create = async_to_raw_response_wrapper( connections.create, ) - self.list = async_to_raw_response_wrapper( - connections.list, - ) self.get = async_to_raw_response_wrapper( connections.get, ) @@ -337,9 +256,6 @@ def __init__(self, connections: ConnectionsResource) -> None: self.create = to_streamed_response_wrapper( connections.create, ) - self.list = to_streamed_response_wrapper( - connections.list, - ) self.get = to_streamed_response_wrapper( connections.get, ) @@ -352,9 +268,6 @@ def __init__(self, connections: AsyncConnectionsResource) -> None: self.create = async_to_streamed_response_wrapper( connections.create, ) - self.list = async_to_streamed_response_wrapper( - connections.list, - ) self.get = async_to_streamed_response_wrapper( connections.get, ) diff --git a/src/supermemory/resources/memories.py b/src/supermemory/resources/memories.py index 180f4cd6..02cf1801 100644 --- a/src/supermemory/resources/memories.py +++ b/src/supermemory/resources/memories.py @@ -2,14 +2,13 @@ from __future__ import annotations -from typing import Dict, List, Union, Mapping, cast -from typing_extensions import Literal +from typing import Dict, List, Union import httpx -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 ..types import memory_add_params, memory_update_params +from .._types import NOT_GIVEN, Body, Query, Headers, NoneType, NotGiven +from .._utils import maybe_transform, async_maybe_transform from .._compat import cached_property from .._resource import SyncAPIResource, AsyncAPIResource from .._response import ( @@ -21,10 +20,7 @@ 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_delete_response import MemoryDeleteResponse from ..types.memory_update_response import MemoryUpdateResponse -from ..types.memory_upload_file_response import MemoryUploadFileResponse __all__ = ["MemoriesResource", "AsyncMemoriesResource"] @@ -55,6 +51,7 @@ def update( *, content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + custom_id: str | NotGiven = NOT_GIVEN, metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -67,6 +64,27 @@ def update( Update a memory with any content type (text, url, file, etc.) and metadata Args: + content: The content to extract and process into a memory. This can be a URL to a + website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + + container_tags: Optional tags this memory should be containerized by. This can be an ID for your + user, a project ID, or any other identifier you wish to use to group memories. + + custom_id: Optional custom ID of the memory. This could be an ID from your database that + will uniquely identify this memory. + + metadata: Optional metadata for the memory. This is used to store additional information + about the memory. You can use this to store any additional information you need + about the memory. Metadata can be filtered through. Keys must be strings and are + case sensitive. Values can be strings, numbers, or booleans. You cannot nest + objects. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -83,6 +101,7 @@ def update( { "content": content, "container_tags": container_tags, + "custom_id": custom_id, "metadata": metadata, }, memory_update_params.MemoryUpdateParams, @@ -93,64 +112,6 @@ def update( cast_to=MemoryUpdateResponse, ) - def list( - self, - *, - filters: str | NotGiven = NOT_GIVEN, - limit: str | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - page: str | NotGiven = NOT_GIVEN, - sort: Literal["createdAt", "updatedAt"] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> MemoryListResponse: - """ - Retrieves a paginated list of memories with their metadata and workflow status - - Args: - filters: Optional filters to apply to the search - - limit: Number of items per page - - order: Sort order - - page: Page number to fetch - - sort: Field to sort by - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - 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/memories", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "filters": filters, - "limit": limit, - "order": order, - "page": page, - "sort": sort, - }, - memory_list_params.MemoryListParams, - ), - ), - cast_to=MemoryListResponse, - ) - def delete( self, id: str, @@ -161,7 +122,7 @@ def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> MemoryDeleteResponse: + ) -> None: """ Delete a memory @@ -176,12 +137,13 @@ def delete( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._delete( f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryDeleteResponse, + cast_to=NoneType, ) def add( @@ -189,6 +151,7 @@ def add( *, content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + custom_id: str | NotGiven = NOT_GIVEN, metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -201,6 +164,27 @@ def add( Add a memory with any content type (text, url, file, etc.) and metadata Args: + content: The content to extract and process into a memory. This can be a URL to a + website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + + container_tags: Optional tags this memory should be containerized by. This can be an ID for your + user, a project ID, or any other identifier you wish to use to group memories. + + custom_id: Optional custom ID of the memory. This could be an ID from your database that + will uniquely identify this memory. + + metadata: Optional metadata for the memory. This is used to store additional information + about the memory. You can use this to store any additional information you need + about the memory. Metadata can be filtered through. Keys must be strings and are + case sensitive. Values can be strings, numbers, or booleans. You cannot nest + objects. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -215,6 +199,7 @@ def add( { "content": content, "container_tags": container_tags, + "custom_id": custom_id, "metadata": metadata, }, memory_add_params.MemoryAddParams, @@ -258,45 +243,6 @@ def get( cast_to=MemoryGetResponse, ) - def upload_file( - self, - *, - file: FileTypes, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> MemoryUploadFileResponse: - """ - Upload a file to be processed - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - body = deepcopy_minimal({"file": file}) - files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return self._post( - "/v3/memories/file", - body=maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams), - files=files, - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=MemoryUploadFileResponse, - ) - class AsyncMemoriesResource(AsyncAPIResource): @cached_property @@ -324,6 +270,7 @@ async def update( *, content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + custom_id: str | NotGiven = NOT_GIVEN, metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -336,6 +283,27 @@ async def update( Update a memory with any content type (text, url, file, etc.) and metadata Args: + content: The content to extract and process into a memory. This can be a URL to a + website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + + container_tags: Optional tags this memory should be containerized by. This can be an ID for your + user, a project ID, or any other identifier you wish to use to group memories. + + custom_id: Optional custom ID of the memory. This could be an ID from your database that + will uniquely identify this memory. + + metadata: Optional metadata for the memory. This is used to store additional information + about the memory. You can use this to store any additional information you need + about the memory. Metadata can be filtered through. Keys must be strings and are + case sensitive. Values can be strings, numbers, or booleans. You cannot nest + objects. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -352,6 +320,7 @@ async def update( { "content": content, "container_tags": container_tags, + "custom_id": custom_id, "metadata": metadata, }, memory_update_params.MemoryUpdateParams, @@ -362,64 +331,6 @@ async def update( cast_to=MemoryUpdateResponse, ) - async def list( - self, - *, - filters: str | NotGiven = NOT_GIVEN, - limit: str | NotGiven = NOT_GIVEN, - order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN, - page: str | NotGiven = NOT_GIVEN, - sort: Literal["createdAt", "updatedAt"] | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> MemoryListResponse: - """ - Retrieves a paginated list of memories with their metadata and workflow status - - Args: - filters: Optional filters to apply to the search - - limit: Number of items per page - - order: Sort order - - page: Page number to fetch - - sort: Field to sort by - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - 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/memories", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "filters": filters, - "limit": limit, - "order": order, - "page": page, - "sort": sort, - }, - memory_list_params.MemoryListParams, - ), - ), - cast_to=MemoryListResponse, - ) - async def delete( self, id: str, @@ -430,7 +341,7 @@ async def delete( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> MemoryDeleteResponse: + ) -> None: """ Delete a memory @@ -445,12 +356,13 @@ async def delete( """ if not id: raise ValueError(f"Expected a non-empty value for `id` but received {id!r}") + extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._delete( f"/v3/memories/{id}", options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryDeleteResponse, + cast_to=NoneType, ) async def add( @@ -458,6 +370,7 @@ async def add( *, content: str, container_tags: List[str] | NotGiven = NOT_GIVEN, + custom_id: str | NotGiven = NOT_GIVEN, metadata: Dict[str, Union[str, float, bool]] | NotGiven = NOT_GIVEN, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -470,6 +383,27 @@ async def add( Add a memory with any content type (text, url, file, etc.) and metadata Args: + content: The content to extract and process into a memory. This can be a URL to a + website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + + container_tags: Optional tags this memory should be containerized by. This can be an ID for your + user, a project ID, or any other identifier you wish to use to group memories. + + custom_id: Optional custom ID of the memory. This could be an ID from your database that + will uniquely identify this memory. + + metadata: Optional metadata for the memory. This is used to store additional information + about the memory. You can use this to store any additional information you need + about the memory. Metadata can be filtered through. Keys must be strings and are + case sensitive. Values can be strings, numbers, or booleans. You cannot nest + objects. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -484,6 +418,7 @@ async def add( { "content": content, "container_tags": container_tags, + "custom_id": custom_id, "metadata": metadata, }, memory_add_params.MemoryAddParams, @@ -527,45 +462,6 @@ async def get( cast_to=MemoryGetResponse, ) - async def upload_file( - self, - *, - file: FileTypes, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> MemoryUploadFileResponse: - """ - Upload a file to be processed - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - body = deepcopy_minimal({"file": file}) - files = extract_files(cast(Mapping[str, object], body), paths=[["file"]]) - # It should be noted that the actual Content-Type header that will be - # sent to the server will contain a `boundary` parameter, e.g. - # multipart/form-data; boundary=---abc-- - extra_headers = {"Content-Type": "multipart/form-data", **(extra_headers or {})} - return await self._post( - "/v3/memories/file", - body=await async_maybe_transform(body, memory_upload_file_params.MemoryUploadFileParams), - files=files, - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=MemoryUploadFileResponse, - ) - class MemoriesResourceWithRawResponse: def __init__(self, memories: MemoriesResource) -> None: @@ -574,9 +470,6 @@ def __init__(self, memories: MemoriesResource) -> None: self.update = to_raw_response_wrapper( memories.update, ) - self.list = to_raw_response_wrapper( - memories.list, - ) self.delete = to_raw_response_wrapper( memories.delete, ) @@ -586,9 +479,6 @@ def __init__(self, memories: MemoriesResource) -> None: self.get = to_raw_response_wrapper( memories.get, ) - self.upload_file = to_raw_response_wrapper( - memories.upload_file, - ) class AsyncMemoriesResourceWithRawResponse: @@ -598,9 +488,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: self.update = async_to_raw_response_wrapper( memories.update, ) - self.list = async_to_raw_response_wrapper( - memories.list, - ) self.delete = async_to_raw_response_wrapper( memories.delete, ) @@ -610,9 +497,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: self.get = async_to_raw_response_wrapper( memories.get, ) - self.upload_file = async_to_raw_response_wrapper( - memories.upload_file, - ) class MemoriesResourceWithStreamingResponse: @@ -622,9 +506,6 @@ def __init__(self, memories: MemoriesResource) -> None: self.update = to_streamed_response_wrapper( memories.update, ) - self.list = to_streamed_response_wrapper( - memories.list, - ) self.delete = to_streamed_response_wrapper( memories.delete, ) @@ -634,9 +515,6 @@ def __init__(self, memories: MemoriesResource) -> None: self.get = to_streamed_response_wrapper( memories.get, ) - self.upload_file = to_streamed_response_wrapper( - memories.upload_file, - ) class AsyncMemoriesResourceWithStreamingResponse: @@ -646,9 +524,6 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: self.update = async_to_streamed_response_wrapper( memories.update, ) - self.list = async_to_streamed_response_wrapper( - memories.list, - ) self.delete = async_to_streamed_response_wrapper( memories.delete, ) @@ -658,6 +533,3 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: self.get = async_to_streamed_response_wrapper( memories.get, ) - self.upload_file = async_to_streamed_response_wrapper( - memories.upload_file, - ) diff --git a/src/supermemory/resources/search.py b/src/supermemory/resources/search.py deleted file mode 100644 index 4e293996..00000000 --- a/src/supermemory/resources/search.py +++ /dev/null @@ -1,296 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List -from typing_extensions import Literal - -import httpx - -from ..types import search_execute_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import maybe_transform, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from .._base_client import make_request_options -from ..types.search_execute_response import SearchExecuteResponse - -__all__ = ["SearchResource", "AsyncSearchResource"] - - -class SearchResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> SearchResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers - """ - return SearchResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> SearchResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response - """ - return SearchResourceWithStreamingResponse(self) - - def execute( - self, - *, - q: str, - categories_filter: List[Literal["technology", "science", "business", "health"]] | NotGiven = NOT_GIVEN, - chunk_threshold: float | NotGiven = NOT_GIVEN, - doc_id: str | NotGiven = NOT_GIVEN, - document_threshold: float | NotGiven = NOT_GIVEN, - filters: search_execute_params.Filters | NotGiven = NOT_GIVEN, - include_summary: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - only_matching_chunks: bool | NotGiven = NOT_GIVEN, - rerank: bool | NotGiven = NOT_GIVEN, - rewrite_query: bool | NotGiven = NOT_GIVEN, - user_id: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SearchExecuteResponse: - """ - Search memories with filtering - - Args: - q: Search query string - - categories_filter: Optional category filters - - chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most - chunks, more results), 1 is most sensitive (returns lesser chunks, accurate - results) - - doc_id: Optional document ID to search within. You can use this to find chunks in a very - large document. - - document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns - most documents, more results), 1 is most sensitive (returns lesser documents, - accurate results) - - filters: Optional filters to apply to the search - - include_summary: If true, include document summary in the response. This is helpful if you want a - chatbot to know the full context of the document. - - limit: Maximum number of results to return - - only_matching_chunks: If true, only return matching chunks without context. Normally, we send the - previous and next chunk to provide more context for LLMs. If you only want the - matching chunk, set this to true. - - rerank: If true, rerank the results based on the query. This is helpful if you want to - ensure the most relevant results are returned. - - rewrite_query: If true, rewrites the query to make it easier to find documents. This increases - the latency by about 400ms - - user_id: End user ID this search is associated with. NOTE: This also acts as a filter for - the search. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._get( - "/v3/search", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "q": q, - "categories_filter": categories_filter, - "chunk_threshold": chunk_threshold, - "doc_id": doc_id, - "document_threshold": document_threshold, - "filters": filters, - "include_summary": include_summary, - "limit": limit, - "only_matching_chunks": only_matching_chunks, - "rerank": rerank, - "rewrite_query": rewrite_query, - "user_id": user_id, - }, - search_execute_params.SearchExecuteParams, - ), - ), - cast_to=SearchExecuteResponse, - ) - - -class AsyncSearchResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncSearchResourceWithRawResponse: - """ - This property can be used as a prefix for any HTTP method call to return - the raw response object instead of the parsed content. - - For more information, see https://www.github.com/supermemoryai/python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncSearchResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncSearchResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/supermemoryai/python-sdk#with_streaming_response - """ - return AsyncSearchResourceWithStreamingResponse(self) - - async def execute( - self, - *, - q: str, - categories_filter: List[Literal["technology", "science", "business", "health"]] | NotGiven = NOT_GIVEN, - chunk_threshold: float | NotGiven = NOT_GIVEN, - doc_id: str | NotGiven = NOT_GIVEN, - document_threshold: float | NotGiven = NOT_GIVEN, - filters: search_execute_params.Filters | NotGiven = NOT_GIVEN, - include_summary: bool | NotGiven = NOT_GIVEN, - limit: int | NotGiven = NOT_GIVEN, - only_matching_chunks: bool | NotGiven = NOT_GIVEN, - rerank: bool | NotGiven = NOT_GIVEN, - rewrite_query: bool | NotGiven = NOT_GIVEN, - user_id: str | NotGiven = NOT_GIVEN, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SearchExecuteResponse: - """ - Search memories with filtering - - Args: - q: Search query string - - categories_filter: Optional category filters - - chunk_threshold: Threshold / sensitivity for chunk selection. 0 is least sensitive (returns most - chunks, more results), 1 is most sensitive (returns lesser chunks, accurate - results) - - doc_id: Optional document ID to search within. You can use this to find chunks in a very - large document. - - document_threshold: Threshold / sensitivity for document selection. 0 is least sensitive (returns - most documents, more results), 1 is most sensitive (returns lesser documents, - accurate results) - - filters: Optional filters to apply to the search - - include_summary: If true, include document summary in the response. This is helpful if you want a - chatbot to know the full context of the document. - - limit: Maximum number of results to return - - only_matching_chunks: If true, only return matching chunks without context. Normally, we send the - previous and next chunk to provide more context for LLMs. If you only want the - matching chunk, set this to true. - - rerank: If true, rerank the results based on the query. This is helpful if you want to - ensure the most relevant results are returned. - - rewrite_query: If true, rewrites the query to make it easier to find documents. This increases - the latency by about 400ms - - user_id: End user ID this search is associated with. NOTE: This also acts as a filter for - the search. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._get( - "/v3/search", - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=await async_maybe_transform( - { - "q": q, - "categories_filter": categories_filter, - "chunk_threshold": chunk_threshold, - "doc_id": doc_id, - "document_threshold": document_threshold, - "filters": filters, - "include_summary": include_summary, - "limit": limit, - "only_matching_chunks": only_matching_chunks, - "rerank": rerank, - "rewrite_query": rewrite_query, - "user_id": user_id, - }, - search_execute_params.SearchExecuteParams, - ), - ), - cast_to=SearchExecuteResponse, - ) - - -class SearchResourceWithRawResponse: - def __init__(self, search: SearchResource) -> None: - self._search = search - - self.execute = to_raw_response_wrapper( - search.execute, - ) - - -class AsyncSearchResourceWithRawResponse: - def __init__(self, search: AsyncSearchResource) -> None: - self._search = search - - self.execute = async_to_raw_response_wrapper( - search.execute, - ) - - -class SearchResourceWithStreamingResponse: - def __init__(self, search: SearchResource) -> None: - self._search = search - - self.execute = to_streamed_response_wrapper( - search.execute, - ) - - -class AsyncSearchResourceWithStreamingResponse: - def __init__(self, search: AsyncSearchResource) -> None: - self._search = search - - self.execute = async_to_streamed_response_wrapper( - search.execute, - ) diff --git a/src/supermemory/resources/settings.py b/src/supermemory/resources/settings.py index 33a331fa..1ae36b65 100644 --- a/src/supermemory/resources/settings.py +++ b/src/supermemory/resources/settings.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List +from typing import Dict, Union, Iterable, Optional import httpx @@ -47,11 +47,20 @@ def with_streaming_response(self) -> SettingsResourceWithStreamingResponse: def update( self, *, - exclude_items: List[str] | NotGiven = NOT_GIVEN, - filter_prompt: str | 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, + exclude_items: Union[str, float, bool, Dict[str, object], Iterable[object], None] | NotGiven = NOT_GIVEN, + filter_prompt: Optional[str] | NotGiven = NOT_GIVEN, + filter_tags: Union[str, float, bool, Dict[str, object], Iterable[object], None] | NotGiven = NOT_GIVEN, + google_drive_client_id: Optional[str] | NotGiven = NOT_GIVEN, + google_drive_client_secret: Optional[str] | NotGiven = NOT_GIVEN, + google_drive_custom_key_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + include_items: Union[str, float, bool, Dict[str, object], Iterable[object], None] | NotGiven = NOT_GIVEN, + notion_client_id: Optional[str] | NotGiven = NOT_GIVEN, + notion_client_secret: Optional[str] | NotGiven = NOT_GIVEN, + notion_custom_key_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + onedrive_client_id: Optional[str] | NotGiven = NOT_GIVEN, + onedrive_client_secret: Optional[str] | NotGiven = NOT_GIVEN, + onedrive_custom_key_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + should_llm_filter: Optional[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, @@ -78,7 +87,16 @@ def update( "exclude_items": exclude_items, "filter_prompt": filter_prompt, "filter_tags": filter_tags, + "google_drive_client_id": google_drive_client_id, + "google_drive_client_secret": google_drive_client_secret, + "google_drive_custom_key_enabled": google_drive_custom_key_enabled, "include_items": include_items, + "notion_client_id": notion_client_id, + "notion_client_secret": notion_client_secret, + "notion_custom_key_enabled": notion_custom_key_enabled, + "onedrive_client_id": onedrive_client_id, + "onedrive_client_secret": onedrive_client_secret, + "onedrive_custom_key_enabled": onedrive_custom_key_enabled, "should_llm_filter": should_llm_filter, }, setting_update_params.SettingUpdateParams, @@ -132,11 +150,20 @@ def with_streaming_response(self) -> AsyncSettingsResourceWithStreamingResponse: async def update( self, *, - exclude_items: List[str] | NotGiven = NOT_GIVEN, - filter_prompt: str | 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, + exclude_items: Union[str, float, bool, Dict[str, object], Iterable[object], None] | NotGiven = NOT_GIVEN, + filter_prompt: Optional[str] | NotGiven = NOT_GIVEN, + filter_tags: Union[str, float, bool, Dict[str, object], Iterable[object], None] | NotGiven = NOT_GIVEN, + google_drive_client_id: Optional[str] | NotGiven = NOT_GIVEN, + google_drive_client_secret: Optional[str] | NotGiven = NOT_GIVEN, + google_drive_custom_key_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + include_items: Union[str, float, bool, Dict[str, object], Iterable[object], None] | NotGiven = NOT_GIVEN, + notion_client_id: Optional[str] | NotGiven = NOT_GIVEN, + notion_client_secret: Optional[str] | NotGiven = NOT_GIVEN, + notion_custom_key_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + onedrive_client_id: Optional[str] | NotGiven = NOT_GIVEN, + onedrive_client_secret: Optional[str] | NotGiven = NOT_GIVEN, + onedrive_custom_key_enabled: Optional[bool] | NotGiven = NOT_GIVEN, + should_llm_filter: Optional[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, @@ -163,7 +190,16 @@ async def update( "exclude_items": exclude_items, "filter_prompt": filter_prompt, "filter_tags": filter_tags, + "google_drive_client_id": google_drive_client_id, + "google_drive_client_secret": google_drive_client_secret, + "google_drive_custom_key_enabled": google_drive_custom_key_enabled, "include_items": include_items, + "notion_client_id": notion_client_id, + "notion_client_secret": notion_client_secret, + "notion_custom_key_enabled": notion_custom_key_enabled, + "onedrive_client_id": onedrive_client_id, + "onedrive_client_secret": onedrive_client_secret, + "onedrive_custom_key_enabled": onedrive_custom_key_enabled, "should_llm_filter": should_llm_filter, }, setting_update_params.SettingUpdateParams, diff --git a/src/supermemory/types/__init__.py b/src/supermemory/types/__init__.py index 2bb705ae..6ca2ae1a 100644 --- a/src/supermemory/types/__init__.py +++ b/src/supermemory/types/__init__.py @@ -3,22 +3,13 @@ from __future__ import annotations from .memory_add_params import MemoryAddParams as MemoryAddParams -from .memory_list_params import MemoryListParams as MemoryListParams from .memory_add_response import MemoryAddResponse as MemoryAddResponse from .memory_get_response import MemoryGetResponse as MemoryGetResponse -from .memory_list_response import MemoryListResponse as MemoryListResponse 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 .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 152cbce3..8bb9e071 100644 --- a/src/supermemory/types/connection_create_params.py +++ b/src/supermemory/types/connection_create_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, Union, Optional +from typing import Dict, List, Union, Optional from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -11,8 +11,10 @@ class ConnectionCreateParams(TypedDict, total=False): - end_user_id: Annotated[str, PropertyInfo(alias="endUserId")] + container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] - redirect_url: Annotated[str, PropertyInfo(alias="redirectUrl")] + document_limit: Annotated[int, PropertyInfo(alias="documentLimit")] metadata: Optional[Dict[str, Union[str, float, bool]]] + + redirect_url: Annotated[str, PropertyInfo(alias="redirectUrl")] diff --git a/src/supermemory/types/connection_get_response.py b/src/supermemory/types/connection_get_response.py index 8f3be8de..69d9e7b0 100644 --- a/src/supermemory/types/connection_get_response.py +++ b/src/supermemory/types/connection_get_response.py @@ -16,6 +16,10 @@ class ConnectionGetResponse(BaseModel): provider: str + document_limit: Optional[float] = FieldInfo(alias="documentLimit", default=None) + + email: Optional[str] = None + 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 deleted file mode 100644 index e027d5f1..00000000 --- a/src/supermemory/types/connection_list_params.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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 deleted file mode 100644 index f68e38b8..00000000 --- a/src/supermemory/types/connection_list_response.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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 index a973e952..b010b941 100644 --- a/src/supermemory/types/memory_add_params.py +++ b/src/supermemory/types/memory_add_params.py @@ -12,7 +12,35 @@ class MemoryAddParams(TypedDict, total=False): content: Required[str] + """The content to extract and process into a memory. + + This can be a URL to a website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + """ container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + """Optional tags this memory should be containerized by. + + This can be an ID for your user, a project ID, or any other identifier you wish + to use to group memories. + """ + + custom_id: Annotated[str, PropertyInfo(alias="customId")] + """Optional custom ID of the memory. + + This could be an ID from your database that will uniquely identify this memory. + """ metadata: Dict[str, Union[str, float, bool]] + """Optional metadata for the memory. + + This is used to store additional information about the memory. You can use this + to store any additional information you need about the memory. Metadata can be + filtered through. Keys must be strings and are case sensitive. Values can be + strings, numbers, or booleans. You cannot nest objects. + """ diff --git a/src/supermemory/types/memory_delete_response.py b/src/supermemory/types/memory_delete_response.py deleted file mode 100644 index 0f17a7e1..00000000 --- a/src/supermemory/types/memory_delete_response.py +++ /dev/null @@ -1,9 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["MemoryDeleteResponse"] - - -class MemoryDeleteResponse(BaseModel): - success: bool diff --git a/src/supermemory/types/memory_get_response.py b/src/supermemory/types/memory_get_response.py index 4d115240..90bf8f31 100644 --- a/src/supermemory/types/memory_get_response.py +++ b/src/supermemory/types/memory_get_response.py @@ -1,5 +1,11 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. +from typing import Dict, List, Union, Optional +from datetime import datetime +from typing_extensions import Literal + +from pydantic import Field as FieldInfo + from .._models import BaseModel __all__ = ["MemoryGetResponse"] @@ -7,5 +13,85 @@ class MemoryGetResponse(BaseModel): id: str + """Unique identifier of the memory.""" + + connection_id: Optional[str] = FieldInfo(alias="connectionId", default=None) + """Optional ID of connection the memory was created from. + + This is useful for identifying the source of the memory. + """ + + content: Optional[str] = None + """The content to extract and process into a memory. + + This can be a URL to a website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + """ + + created_at: datetime = FieldInfo(alias="createdAt") + """Creation timestamp""" + + custom_id: Optional[str] = FieldInfo(alias="customId", default=None) + """Optional custom ID of the memory. + + This could be an ID from your database that will uniquely identify this memory. + """ + + metadata: Union[str, float, bool, Dict[str, object], List[object], None] = None + """Optional metadata for the memory. + + This is used to store additional information about the memory. You can use this + to store any additional information you need about the memory. Metadata can be + filtered through. Keys must be strings and are case sensitive. Values can be + strings, numbers, or booleans. You cannot nest objects. + """ + + og_image: Optional[str] = FieldInfo(alias="ogImage", default=None) + + source: Optional[str] = None + """Source of the memory""" + + status: Literal["unknown", "queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"] + """Status of the memory""" + + summary: Optional[str] = None + """Summary of the memory content""" + + title: Optional[str] = None + """Title of the memory""" + + type: Literal[ + "text", + "pdf", + "tweet", + "google_doc", + "google_slide", + "google_sheet", + "image", + "video", + "notion_doc", + "webpage", + "onedrive", + ] + """Type of the memory""" + + updated_at: datetime = FieldInfo(alias="updatedAt") + """Last update timestamp""" + + url: Optional[str] = None + """URL of the memory""" + + container_tags: Optional[List[str]] = FieldInfo(alias="containerTags", default=None) + """Optional tags this memory should be containerized by. + + This can be an ID for your user, a project ID, or any other identifier you wish + to use to group memories. + """ - status: str + raw: None = None + """Raw content of the memory""" diff --git a/src/supermemory/types/memory_list_params.py b/src/supermemory/types/memory_list_params.py deleted file mode 100644 index d40690da..00000000 --- a/src/supermemory/types/memory_list_params.py +++ /dev/null @@ -1,24 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing_extensions import Literal, TypedDict - -__all__ = ["MemoryListParams"] - - -class MemoryListParams(TypedDict, total=False): - filters: str - """Optional filters to apply to the search""" - - limit: str - """Number of items per page""" - - order: Literal["asc", "desc"] - """Sort order""" - - page: str - """Page number to fetch""" - - sort: Literal["createdAt", "updatedAt"] - """Field to sort by""" diff --git a/src/supermemory/types/memory_list_response.py b/src/supermemory/types/memory_list_response.py deleted file mode 100644 index 5b325c78..00000000 --- a/src/supermemory/types/memory_list_response.py +++ /dev/null @@ -1,95 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Union, Optional -from datetime import datetime -from typing_extensions import Literal - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["MemoryListResponse", "Memory", "Pagination"] - - -class Memory(BaseModel): - id: str - """Unique identifier of the memory.""" - - content: Optional[str] = None - """The content to extract and process into a memory. - - This can be a URL to a website, a PDF, an image, or a video. - - Plaintext: Any plaintext format - - URL: A URL to a website, PDF, image, or video - - We automatically detect the content type from the url's response format. - """ - - created_at: datetime = FieldInfo(alias="createdAt") - """Creation timestamp""" - - custom_id: Optional[str] = FieldInfo(alias="customId", default=None) - """Optional custom ID of the memory. - - This could be an ID from your database that will uniquely identify this memory. - """ - - metadata: Union[str, float, bool, Dict[str, object], List[object], None] = None - """Optional metadata for the memory. - - This is used to store additional information about the memory. You can use this - to store any additional information you need about the memory. Metadata can be - filtered through. Keys must be strings and are case sensitive. Values can be - strings, numbers, or booleans. You cannot nest objects. - """ - - og_image: Optional[str] = FieldInfo(alias="ogImage", default=None) - - source: Optional[str] = None - - status: Literal["unknown", "queued", "extracting", "chunking", "embedding", "indexing", "done", "failed"] - """Status of the memory""" - - summary: Optional[str] = None - """Summary of the memory content""" - - title: Optional[str] = None - """Title of the memory""" - - type: Literal["text", "pdf", "tweet", "google_doc", "image", "video", "notion_doc", "webpage"] - """Type of the memory""" - - updated_at: datetime = FieldInfo(alias="updatedAt") - """Last update timestamp""" - - url: Optional[str] = None - """URL of the memory""" - - container_tags: Optional[List[str]] = FieldInfo(alias="containerTags", default=None) - """Optional tags this memory should be containerized by. - - This can be an ID for your user, a project ID, or any other identifier you wish - to use to group memories. - """ - - raw: None = None - """Raw content of the memory""" - - -class Pagination(BaseModel): - current_page: float = FieldInfo(alias="currentPage") - - limit: float - - total_items: float = FieldInfo(alias="totalItems") - - total_pages: float = FieldInfo(alias="totalPages") - - -class MemoryListResponse(BaseModel): - memories: List[Memory] - - pagination: Pagination - """Pagination metadata""" diff --git a/src/supermemory/types/memory_update_params.py b/src/supermemory/types/memory_update_params.py index 2e94bfe3..2dc1d45d 100644 --- a/src/supermemory/types/memory_update_params.py +++ b/src/supermemory/types/memory_update_params.py @@ -12,7 +12,35 @@ class MemoryUpdateParams(TypedDict, total=False): content: Required[str] + """The content to extract and process into a memory. + + This can be a URL to a website, a PDF, an image, or a video. + + Plaintext: Any plaintext format + + URL: A URL to a website, PDF, image, or video + + We automatically detect the content type from the url's response format. + """ container_tags: Annotated[List[str], PropertyInfo(alias="containerTags")] + """Optional tags this memory should be containerized by. + + This can be an ID for your user, a project ID, or any other identifier you wish + to use to group memories. + """ + + custom_id: Annotated[str, PropertyInfo(alias="customId")] + """Optional custom ID of the memory. + + This could be an ID from your database that will uniquely identify this memory. + """ metadata: Dict[str, Union[str, float, bool]] + """Optional metadata for the memory. + + This is used to store additional information about the memory. You can use this + to store any additional information you need about the memory. Metadata can be + filtered through. Keys must be strings and are case sensitive. Values can be + strings, numbers, or booleans. You cannot nest objects. + """ diff --git a/src/supermemory/types/memory_upload_file_params.py b/src/supermemory/types/memory_upload_file_params.py deleted file mode 100644 index aa6c082a..00000000 --- a/src/supermemory/types/memory_upload_file_params.py +++ /dev/null @@ -1,13 +0,0 @@ -# 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 deleted file mode 100644 index f67b958f..00000000 --- a/src/supermemory/types/memory_upload_file_response.py +++ /dev/null @@ -1,11 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from .._models import BaseModel - -__all__ = ["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 deleted file mode 100644 index bc3b4756..00000000 --- a/src/supermemory/types/search_execute_params.py +++ /dev/null @@ -1,86 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Dict, List, Union, Iterable -from typing_extensions import Literal, Required, Annotated, TypeAlias, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["SearchExecuteParams", "Filters", "FiltersUnionMember0"] - - -class SearchExecuteParams(TypedDict, total=False): - q: Required[str] - """Search query string""" - - categories_filter: Annotated[ - List[Literal["technology", "science", "business", "health"]], PropertyInfo(alias="categoriesFilter") - ] - """Optional category filters""" - - chunk_threshold: Annotated[float, PropertyInfo(alias="chunkThreshold")] - """Threshold / sensitivity for chunk selection. - - 0 is least sensitive (returns most chunks, more results), 1 is most sensitive - (returns lesser chunks, accurate results) - """ - - doc_id: Annotated[str, PropertyInfo(alias="docId")] - """Optional document ID to search within. - - You can use this to find chunks in a very large document. - """ - - document_threshold: Annotated[float, PropertyInfo(alias="documentThreshold")] - """Threshold / sensitivity for document selection. - - 0 is least sensitive (returns most documents, more results), 1 is most sensitive - (returns lesser documents, accurate results) - """ - - filters: Filters - """Optional filters to apply to the search""" - - include_summary: Annotated[bool, PropertyInfo(alias="includeSummary")] - """If true, include document summary in the response. - - This is helpful if you want a chatbot to know the full context of the document. - """ - - limit: int - """Maximum number of results to return""" - - only_matching_chunks: Annotated[bool, PropertyInfo(alias="onlyMatchingChunks")] - """If true, only return matching chunks without context. - - Normally, we send the previous and next chunk to provide more context for LLMs. - If you only want the matching chunk, set this to true. - """ - - rerank: bool - """If true, rerank the results based on the query. - - This is helpful if you want to ensure the most relevant results are returned. - """ - - rewrite_query: Annotated[bool, PropertyInfo(alias="rewriteQuery")] - """If true, rewrites the query to make it easier to find documents. - - This increases the latency by about 400ms - """ - - user_id: Annotated[str, PropertyInfo(alias="userId")] - """End user ID this search is associated with. - - NOTE: This also acts as a filter for the search. - """ - - -class FiltersUnionMember0(TypedDict, total=False): - and_: Annotated[Iterable[object], PropertyInfo(alias="AND")] - - or_: Annotated[Iterable[object], PropertyInfo(alias="OR")] - - -Filters: TypeAlias = Union[FiltersUnionMember0, Dict[str, object]] diff --git a/src/supermemory/types/search_execute_response.py b/src/supermemory/types/search_execute_response.py deleted file mode 100644 index 5b2bc4e0..00000000 --- a/src/supermemory/types/search_execute_response.py +++ /dev/null @@ -1,52 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional -from datetime import datetime - -from pydantic import Field as FieldInfo - -from .._models import BaseModel - -__all__ = ["SearchExecuteResponse", "Result", "ResultChunk"] - - -class ResultChunk(BaseModel): - content: str - """Content of the matching chunk""" - - is_relevant: bool = FieldInfo(alias="isRelevant") - """Whether this chunk is relevant to the query""" - - score: float - """Similarity score for this chunk""" - - -class Result(BaseModel): - chunks: List[ResultChunk] - """Matching content chunks from the document""" - - created_at: datetime = FieldInfo(alias="createdAt") - """Document creation date""" - - document_id: str = FieldInfo(alias="documentId") - """ID of the matching document""" - - metadata: Optional[Dict[str, object]] = None - """Document metadata""" - - score: float - """Relevance score of the match""" - - title: str - """Document title""" - - updated_at: datetime = FieldInfo(alias="updatedAt") - """Document last update date""" - - -class SearchExecuteResponse(BaseModel): - results: List[Result] - - timing: float - - total: float diff --git a/src/supermemory/types/setting_get_response.py b/src/supermemory/types/setting_get_response.py index 0cf25fd9..32bdbe57 100644 --- a/src/supermemory/types/setting_get_response.py +++ b/src/supermemory/types/setting_get_response.py @@ -1,6 +1,8 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict +from typing import Dict, List, Union, Optional + +from pydantic import Field as FieldInfo from .._models import BaseModel @@ -8,4 +10,36 @@ class SettingGetResponse(BaseModel): - settings: Dict[str, object] + exclude_items: Union[str, float, bool, Dict[str, object], List[object], None] = FieldInfo( + alias="excludeItems", default=None + ) + + filter_prompt: Optional[str] = FieldInfo(alias="filterPrompt", default=None) + + filter_tags: Union[str, float, bool, Dict[str, object], List[object], None] = FieldInfo( + alias="filterTags", default=None + ) + + google_drive_client_id: Optional[str] = FieldInfo(alias="googleDriveClientId", default=None) + + google_drive_client_secret: Optional[str] = FieldInfo(alias="googleDriveClientSecret", default=None) + + google_drive_custom_key_enabled: Optional[bool] = FieldInfo(alias="googleDriveCustomKeyEnabled", default=None) + + include_items: Union[str, float, bool, Dict[str, object], List[object], None] = FieldInfo( + alias="includeItems", default=None + ) + + notion_client_id: Optional[str] = FieldInfo(alias="notionClientId", default=None) + + notion_client_secret: Optional[str] = FieldInfo(alias="notionClientSecret", default=None) + + notion_custom_key_enabled: Optional[bool] = FieldInfo(alias="notionCustomKeyEnabled", default=None) + + onedrive_client_id: Optional[str] = FieldInfo(alias="onedriveClientId", default=None) + + onedrive_client_secret: Optional[str] = FieldInfo(alias="onedriveClientSecret", default=None) + + onedrive_custom_key_enabled: Optional[bool] = FieldInfo(alias="onedriveCustomKeyEnabled", default=None) + + should_llm_filter: Optional[bool] = FieldInfo(alias="shouldLLMFilter", default=None) diff --git a/src/supermemory/types/setting_update_params.py b/src/supermemory/types/setting_update_params.py index 8f19a083..16263ee7 100644 --- a/src/supermemory/types/setting_update_params.py +++ b/src/supermemory/types/setting_update_params.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Dict, List +from typing import Dict, Union, Iterable, Optional from typing_extensions import Annotated, TypedDict from .._utils import PropertyInfo @@ -11,12 +11,36 @@ class SettingUpdateParams(TypedDict, total=False): - exclude_items: Annotated[List[str], PropertyInfo(alias="excludeItems")] + exclude_items: Annotated[ + Union[str, float, bool, Dict[str, object], Iterable[object], None], PropertyInfo(alias="excludeItems") + ] - filter_prompt: Annotated[str, PropertyInfo(alias="filterPrompt")] + filter_prompt: Annotated[Optional[str], PropertyInfo(alias="filterPrompt")] - filter_tags: Annotated[Dict[str, List[str]], PropertyInfo(alias="filterTags")] + filter_tags: Annotated[ + Union[str, float, bool, Dict[str, object], Iterable[object], None], PropertyInfo(alias="filterTags") + ] - include_items: Annotated[List[str], PropertyInfo(alias="includeItems")] + google_drive_client_id: Annotated[Optional[str], PropertyInfo(alias="googleDriveClientId")] - should_llm_filter: Annotated[bool, PropertyInfo(alias="shouldLLMFilter")] + google_drive_client_secret: Annotated[Optional[str], PropertyInfo(alias="googleDriveClientSecret")] + + google_drive_custom_key_enabled: Annotated[Optional[bool], PropertyInfo(alias="googleDriveCustomKeyEnabled")] + + include_items: Annotated[ + Union[str, float, bool, Dict[str, object], Iterable[object], None], PropertyInfo(alias="includeItems") + ] + + notion_client_id: Annotated[Optional[str], PropertyInfo(alias="notionClientId")] + + notion_client_secret: Annotated[Optional[str], PropertyInfo(alias="notionClientSecret")] + + notion_custom_key_enabled: Annotated[Optional[bool], PropertyInfo(alias="notionCustomKeyEnabled")] + + onedrive_client_id: Annotated[Optional[str], PropertyInfo(alias="onedriveClientId")] + + onedrive_client_secret: Annotated[Optional[str], PropertyInfo(alias="onedriveClientSecret")] + + onedrive_custom_key_enabled: Annotated[Optional[bool], PropertyInfo(alias="onedriveCustomKeyEnabled")] + + should_llm_filter: Annotated[Optional[bool], PropertyInfo(alias="shouldLLMFilter")] diff --git a/src/supermemory/types/setting_update_response.py b/src/supermemory/types/setting_update_response.py index 58a717c0..3770b38a 100644 --- a/src/supermemory/types/setting_update_response.py +++ b/src/supermemory/types/setting_update_response.py @@ -1,27 +1,53 @@ # 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 pydantic import Field as FieldInfo from .._models import BaseModel -__all__ = ["SettingUpdateResponse", "Settings"] +__all__ = ["SettingUpdateResponse", "Updated"] -class Settings(BaseModel): - exclude_items: Optional[List[str]] = FieldInfo(alias="excludeItems", default=None) +class Updated(BaseModel): + exclude_items: Union[str, float, bool, Dict[str, object], List[object], None] = FieldInfo( + alias="excludeItems", default=None + ) filter_prompt: Optional[str] = FieldInfo(alias="filterPrompt", default=None) - filter_tags: Optional[Dict[str, List[str]]] = FieldInfo(alias="filterTags", default=None) + filter_tags: Union[str, float, bool, Dict[str, object], List[object], None] = FieldInfo( + alias="filterTags", default=None + ) - include_items: Optional[List[str]] = FieldInfo(alias="includeItems", default=None) + google_drive_client_id: Optional[str] = FieldInfo(alias="googleDriveClientId", default=None) + + google_drive_client_secret: Optional[str] = FieldInfo(alias="googleDriveClientSecret", default=None) + + google_drive_custom_key_enabled: Optional[bool] = FieldInfo(alias="googleDriveCustomKeyEnabled", default=None) + + include_items: Union[str, float, bool, Dict[str, object], List[object], None] = FieldInfo( + alias="includeItems", default=None + ) + + notion_client_id: Optional[str] = FieldInfo(alias="notionClientId", default=None) + + notion_client_secret: Optional[str] = FieldInfo(alias="notionClientSecret", default=None) + + notion_custom_key_enabled: Optional[bool] = FieldInfo(alias="notionCustomKeyEnabled", default=None) + + onedrive_client_id: Optional[str] = FieldInfo(alias="onedriveClientId", default=None) + + onedrive_client_secret: Optional[str] = FieldInfo(alias="onedriveClientSecret", default=None) + + onedrive_custom_key_enabled: Optional[bool] = FieldInfo(alias="onedriveCustomKeyEnabled", default=None) should_llm_filter: Optional[bool] = FieldInfo(alias="shouldLLMFilter", default=None) class SettingUpdateResponse(BaseModel): - message: str + org_id: str = FieldInfo(alias="orgId") + + org_slug: str = FieldInfo(alias="orgSlug") - settings: Settings + updated: Updated diff --git a/tests/api_resources/test_connections.py b/tests/api_resources/test_connections.py index aee15f42..bb4ed3e0 100644 --- a/tests/api_resources/test_connections.py +++ b/tests/api_resources/test_connections.py @@ -9,11 +9,7 @@ from supermemory import Supermemory, AsyncSupermemory from tests.utils import assert_matches_type -from supermemory.types import ( - ConnectionGetResponse, - ConnectionListResponse, - ConnectionCreateResponse, -) +from supermemory.types import ConnectionGetResponse, ConnectionCreateResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -34,9 +30,10 @@ def test_method_create(self, client: Supermemory) -> None: def test_method_create_with_all_params(self, client: Supermemory) -> None: connection = client.connections.create( provider="notion", - end_user_id="endUserId", - redirect_url="redirectUrl", + container_tags=["string"], + document_limit=1, metadata={"foo": "string"}, + redirect_url="redirectUrl", ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @@ -66,42 +63,6 @@ def test_streaming_response_create(self, client: Supermemory) -> None: 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: @@ -146,7 +107,9 @@ def test_path_params_get(self, client: Supermemory) -> None: class TestAsyncConnections: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @pytest.mark.skip() @parametrize @@ -161,9 +124,10 @@ async def test_method_create(self, async_client: AsyncSupermemory) -> None: 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", + container_tags=["string"], + document_limit=1, metadata={"foo": "string"}, + redirect_url="redirectUrl", ) assert_matches_type(ConnectionCreateResponse, connection, path=["response"]) @@ -193,42 +157,6 @@ async def test_streaming_response_create(self, async_client: AsyncSupermemory) - 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: diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 8e7130f2..c398ab8f 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -12,10 +12,7 @@ from supermemory.types import ( MemoryAddResponse, MemoryGetResponse, - MemoryListResponse, - MemoryDeleteResponse, MemoryUpdateResponse, - MemoryUploadFileResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -39,7 +36,8 @@ 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"], + container_tags=["user_123", "project_123"], + custom_id="mem_abc123", metadata={ "source": "web", "category": "technology", @@ -88,53 +86,13 @@ def test_path_params_update(self, client: Supermemory) -> None: content="This is a detailed article about machine learning concepts...", ) - @pytest.mark.skip() - @parametrize - def test_method_list(self, client: Supermemory) -> None: - memory = client.memories.list() - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_list_with_all_params(self, client: Supermemory) -> None: - memory = client.memories.list( - filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', - limit="10", - order="desc", - page="1", - sort="createdAt", - ) - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_list(self, client: Supermemory) -> None: - response = client.memories.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_list(self, client: Supermemory) -> None: - with client.memories.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip() @parametrize def test_method_delete(self, client: Supermemory) -> None: memory = client.memories.delete( "id", ) - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert memory is None @pytest.mark.skip() @parametrize @@ -146,7 +104,7 @@ def test_raw_response_delete(self, client: Supermemory) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert memory is None @pytest.mark.skip() @parametrize @@ -158,7 +116,7 @@ def test_streaming_response_delete(self, client: Supermemory) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert memory is None assert cast(Any, response.is_closed) is True @@ -183,7 +141,8 @@ def test_method_add(self, client: Supermemory) -> None: 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"], + container_tags=["user_123", "project_123"], + custom_id="mem_abc123", metadata={ "source": "web", "category": "technology", @@ -263,43 +222,11 @@ def test_path_params_get(self, client: Supermemory) -> None: "", ) - @pytest.mark.skip() - @parametrize - def test_method_upload_file(self, client: Supermemory) -> None: - memory = client.memories.upload_file( - file=b"raw file contents", - ) - assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_upload_file(self, client: Supermemory) -> None: - response = client.memories.with_raw_response.upload_file( - file=b"raw file contents", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = response.parse() - assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_upload_file(self, client: Supermemory) -> None: - with client.memories.with_streaming_response.upload_file( - file=b"raw file contents", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = response.parse() - assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) - - assert cast(Any, response.is_closed) is True - class TestAsyncMemories: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @pytest.mark.skip() @parametrize @@ -316,7 +243,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncSupermemor memory = await async_client.memories.update( id="id", content="This is a detailed article about machine learning concepts...", - container_tags=["string"], + container_tags=["user_123", "project_123"], + custom_id="mem_abc123", metadata={ "source": "web", "category": "technology", @@ -365,53 +293,13 @@ async def test_path_params_update(self, async_client: AsyncSupermemory) -> None: 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.memories.list() - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memories.list( - filters='{"AND":[{"key":"group","value":"jira_users","negate":false},{"filterType":"numeric","key":"timestamp","value":"1742745777","negate":false,"numericOperator":">"}]}', - limit="10", - order="desc", - page="1", - sort="createdAt", - ) - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_list(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memories.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = await response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_list(self, async_client: AsyncSupermemory) -> None: - 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" - - memory = await response.parse() - assert_matches_type(MemoryListResponse, memory, path=["response"]) - - assert cast(Any, response.is_closed) is True - @pytest.mark.skip() @parametrize async def test_method_delete(self, async_client: AsyncSupermemory) -> None: memory = await async_client.memories.delete( "id", ) - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert memory is None @pytest.mark.skip() @parametrize @@ -423,7 +311,7 @@ async def test_raw_response_delete(self, async_client: AsyncSupermemory) -> None assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert memory is None @pytest.mark.skip() @parametrize @@ -435,7 +323,7 @@ async def test_streaming_response_delete(self, async_client: AsyncSupermemory) - assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryDeleteResponse, memory, path=["response"]) + assert memory is None assert cast(Any, response.is_closed) is True @@ -460,7 +348,8 @@ async def test_method_add(self, async_client: AsyncSupermemory) -> None: 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"], + container_tags=["user_123", "project_123"], + custom_id="mem_abc123", metadata={ "source": "web", "category": "technology", @@ -539,37 +428,3 @@ async def test_path_params_get(self, async_client: AsyncSupermemory) -> None: await async_client.memories.with_raw_response.get( "", ) - - @pytest.mark.skip() - @parametrize - async def test_method_upload_file(self, async_client: AsyncSupermemory) -> None: - memory = await async_client.memories.upload_file( - file=b"raw file contents", - ) - assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_upload_file(self, async_client: AsyncSupermemory) -> None: - response = await async_client.memories.with_raw_response.upload_file( - file=b"raw file contents", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = await response.parse() - assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_upload_file(self, async_client: AsyncSupermemory) -> None: - async with async_client.memories.with_streaming_response.upload_file( - file=b"raw file contents", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = await response.parse() - assert_matches_type(MemoryUploadFileResponse, memory, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_search.py b/tests/api_resources/test_search.py deleted file mode 100644 index c0557ff5..00000000 --- a/tests/api_resources/test_search.py +++ /dev/null @@ -1,160 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -import os -from typing import Any, cast - -import pytest - -from supermemory import Supermemory, AsyncSupermemory -from tests.utils import assert_matches_type -from supermemory.types import SearchExecuteResponse - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestSearch: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - def test_method_execute(self, client: Supermemory) -> None: - search = client.search.execute( - q="machine learning concepts", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_method_execute_with_all_params(self, client: Supermemory) -> None: - search = client.search.execute( - q="machine learning concepts", - categories_filter=["technology", "science"], - chunk_threshold=0.5, - doc_id="doc_xyz789", - document_threshold=0.5, - filters={ - "and_": [ - { - "key": "group", - "value": "jira_users", - "negate": False, - }, - { - "filterType": "numeric", - "key": "timestamp", - "value": "1742745777", - "negate": False, - "numericOperator": ">", - }, - ], - "or_": [{}], - }, - include_summary=False, - limit=10, - only_matching_chunks=False, - rerank=False, - rewrite_query=False, - user_id="user_123", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_raw_response_execute(self, client: Supermemory) -> None: - response = client.search.with_raw_response.execute( - q="machine learning concepts", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - search = response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - def test_streaming_response_execute(self, client: Supermemory) -> None: - with client.search.with_streaming_response.execute( - q="machine learning concepts", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - search = response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncSearch: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) - - @pytest.mark.skip() - @parametrize - async def test_method_execute(self, async_client: AsyncSupermemory) -> None: - search = await async_client.search.execute( - q="machine learning concepts", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_method_execute_with_all_params(self, async_client: AsyncSupermemory) -> None: - search = await async_client.search.execute( - q="machine learning concepts", - categories_filter=["technology", "science"], - chunk_threshold=0.5, - doc_id="doc_xyz789", - document_threshold=0.5, - filters={ - "and_": [ - { - "key": "group", - "value": "jira_users", - "negate": False, - }, - { - "filterType": "numeric", - "key": "timestamp", - "value": "1742745777", - "negate": False, - "numericOperator": ">", - }, - ], - "or_": [{}], - }, - include_summary=False, - limit=10, - only_matching_chunks=False, - rerank=False, - rewrite_query=False, - user_id="user_123", - ) - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_raw_response_execute(self, async_client: AsyncSupermemory) -> None: - response = await async_client.search.with_raw_response.execute( - q="machine learning concepts", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - search = await response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - @pytest.mark.skip() - @parametrize - async def test_streaming_response_execute(self, async_client: AsyncSupermemory) -> None: - async with async_client.search.with_streaming_response.execute( - q="machine learning concepts", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - search = await response.parse() - assert_matches_type(SearchExecuteResponse, search, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_settings.py b/tests/api_resources/test_settings.py index a7a8a777..ac790b15 100644 --- a/tests/api_resources/test_settings.py +++ b/tests/api_resources/test_settings.py @@ -27,10 +27,19 @@ def test_method_update(self, client: Supermemory) -> None: @parametrize def test_method_update_with_all_params(self, client: Supermemory) -> None: setting = client.settings.update( - exclude_items=["x"], - filter_prompt="x", - filter_tags={"foo": ["string"]}, - include_items=["x"], + exclude_items="string", + filter_prompt="filterPrompt", + filter_tags="string", + google_drive_client_id="googleDriveClientId", + google_drive_client_secret="googleDriveClientSecret", + google_drive_custom_key_enabled=True, + include_items="string", + notion_client_id="notionClientId", + notion_client_secret="notionClientSecret", + notion_custom_key_enabled=True, + onedrive_client_id="onedriveClientId", + onedrive_client_secret="onedriveClientSecret", + onedrive_custom_key_enabled=True, should_llm_filter=True, ) assert_matches_type(SettingUpdateResponse, setting, path=["response"]) @@ -87,7 +96,9 @@ def test_streaming_response_get(self, client: Supermemory) -> None: class TestAsyncSettings: - parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"]) + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) @pytest.mark.skip() @parametrize @@ -99,10 +110,19 @@ 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( - exclude_items=["x"], - filter_prompt="x", - filter_tags={"foo": ["string"]}, - include_items=["x"], + exclude_items="string", + filter_prompt="filterPrompt", + filter_tags="string", + google_drive_client_id="googleDriveClientId", + google_drive_client_secret="googleDriveClientSecret", + google_drive_custom_key_enabled=True, + include_items="string", + notion_client_id="notionClientId", + notion_client_secret="notionClientSecret", + notion_custom_key_enabled=True, + onedrive_client_id="onedriveClientId", + onedrive_client_secret="onedriveClientSecret", + onedrive_custom_key_enabled=True, should_llm_filter=True, ) assert_matches_type(SettingUpdateResponse, setting, path=["response"]) diff --git a/tests/conftest.py b/tests/conftest.py index 44fc9e09..2baaab81 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,17 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + from __future__ import annotations import os import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from supermemory import Supermemory, AsyncSupermemory +from supermemory import Supermemory, AsyncSupermemory, DefaultAioHttpClient +from supermemory._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -25,6 +29,19 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: for async_test in pytest_asyncio_tests: async_test.add_marker(session_scope_marker, append=False) + # We skip tests that use both the aiohttp client and respx_mock as respx_mock + # doesn't support custom transports. + for item in items: + if "async_client" not in item.fixturenames or "respx_mock" not in item.fixturenames: + continue + + if not hasattr(item, "callspec"): + continue + + async_client_param = item.callspec.params.get("async_client") + if is_dict(async_client_param) and async_client_param.get("http_client") == "aiohttp": + item.add_marker(pytest.mark.skip(reason="aiohttp client is not compatible with respx_mock")) + base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -43,9 +60,25 @@ def client(request: FixtureRequest) -> Iterator[Supermemory]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncSupermemory]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - - async with AsyncSupermemory(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + param = getattr(request, "param", True) + + # defaults + strict = True + http_client: None | httpx.AsyncClient = None + + if isinstance(param, bool): + strict = param + elif is_dict(param): + strict = param.get("strict", True) + assert isinstance(strict, bool) + + http_client_type = param.get("http_client", "httpx") + if http_client_type == "aiohttp": + http_client = DefaultAioHttpClient() + else: + raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") + + async with AsyncSupermemory( + base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client + ) as client: yield client diff --git a/tests/test_client.py b/tests/test_client.py index 3636a001..ecc086ec 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -23,17 +23,16 @@ from supermemory import Supermemory, AsyncSupermemory, APIResponseValidationError from supermemory._types import Omit -from supermemory._utils import maybe_transform from supermemory._models import BaseModel, FinalRequestOptions -from supermemory._constants import RAW_RESPONSE_HEADER from supermemory._exceptions import APIStatusError, APITimeoutError, SupermemoryError, APIResponseValidationError from supermemory._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, BaseClient, + DefaultHttpxClient, + DefaultAsyncHttpxClient, make_request_options, ) -from supermemory.types.memory_add_params import MemoryAddParams from .utils import update_env @@ -192,6 +191,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -723,42 +723,25 @@ 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: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Supermemory) -> None: respx_mock.post("/v3/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - self.client.post( - "/v3/memories", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + client.memories.with_streaming_response.add( + content="This is a detailed article about machine learning concepts..." + ).__enter__() assert _get_open_connections(self.client) == 0 @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: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Supermemory) -> None: respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - self.client.post( - "/v3/memories", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + client.memories.with_streaming_response.add( + content="This is a detailed article about machine learning concepts..." + ).__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -846,6 +829,55 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: assert response.http_request.headers.get("x-stainless-retry-count") == "42" + def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" + class TestAsyncSupermemory: client = AsyncSupermemory(base_url=base_url, api_key=api_key, _strict_response_validation=True) @@ -982,6 +1014,7 @@ def test_copy_signature(self) -> None: copy_param = copy_signature.parameters.get(name) assert copy_param is not None, f"copy() signature is missing the {name} param" + @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") def test_copy_build_request(self) -> None: options = FinalRequestOptions(method="get", url="/foo") @@ -1517,42 +1550,29 @@ 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: + async def test_retrying_timeout_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncSupermemory + ) -> None: respx_mock.post("/v3/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await self.client.post( - "/v3/memories", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) + await async_client.memories.with_streaming_response.add( + content="This is a detailed article about machine learning concepts..." + ).__aenter__() assert _get_open_connections(self.client) == 0 @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: + async def test_retrying_status_errors_doesnt_leak( + self, respx_mock: MockRouter, async_client: AsyncSupermemory + ) -> None: respx_mock.post("/v3/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await self.client.post( - "/v3/memories", - body=cast( - object, - maybe_transform( - dict(content="This is a detailed article about machine learning concepts..."), MemoryAddParams - ), - ), - cast_to=httpx.Response, - options={"headers": {RAW_RESPONSE_HEADER: "stream"}}, - ) - + await async_client.memories.with_streaming_response.add( + content="This is a detailed article about machine learning concepts..." + ).__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1687,3 +1707,52 @@ async def test_main() -> None: raise AssertionError("calling get_platform using asyncify resulted in a hung process") time.sleep(0.1) + + async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: + # Test that the proxy environment variables are set correctly + monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + + client = DefaultAsyncHttpxClient() + + mounts = tuple(client._mounts.items()) + assert len(mounts) == 1 + assert mounts[0][0].pattern == "https://" + + @pytest.mark.filterwarnings("ignore:.*deprecated.*:DeprecationWarning") + async def test_default_client_creation(self) -> None: + # Ensure that the client can be initialized without any exceptions + DefaultAsyncHttpxClient( + verify=True, + cert=None, + trust_env=True, + http1=True, + http2=False, + limits=httpx.Limits(max_connections=100, max_keepalive_connections=20), + ) + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects(self, respx_mock: MockRouter) -> None: + # Test that the default follow_redirects=True allows following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + respx_mock.get("/redirected").mock(return_value=httpx.Response(200, json={"status": "ok"})) + + response = await self.client.post("/redirect", body={"key": "value"}, cast_to=httpx.Response) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + @pytest.mark.respx(base_url=base_url) + async def test_follow_redirects_disabled(self, respx_mock: MockRouter) -> None: + # Test that follow_redirects=False prevents following redirects + respx_mock.post("/redirect").mock( + return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) + ) + + with pytest.raises(APIStatusError) as exc_info: + await self.client.post( + "/redirect", body={"key": "value"}, options={"follow_redirects": False}, cast_to=httpx.Response + ) + + assert exc_info.value.response.status_code == 302 + assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected"