From c0845dc8133eb74e72f18651e9da53102609d833 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 21 Jun 2025 04:20:19 +0000 Subject: [PATCH 01/33] feat(client): add support for aiohttp --- README.md | 34 ++++++++++++++++ pyproject.toml | 2 + requirements-dev.lock | 27 +++++++++++++ requirements.lock | 27 +++++++++++++ src/hyperspell/__init__.py | 3 +- src/hyperspell/_base_client.py | 22 +++++++++++ .../integrations/test_google_calendar.py | 4 +- .../integrations/test_web_crawler.py | 4 +- tests/api_resources/test_auth.py | 4 +- tests/api_resources/test_collections.py | 4 +- tests/api_resources/test_documents.py | 4 +- tests/api_resources/test_integrations.py | 4 +- tests/api_resources/test_query.py | 4 +- tests/conftest.py | 39 ++++++++++++++++--- 14 files changed, 169 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index cc22c6ee..fc3b9310 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,40 @@ asyncio.run(main()) Functionality between the synchronous and asynchronous clients is otherwise identical. +### With aiohttp + +By default, the async client uses `httpx` for HTTP requests. However, for improved concurrency performance you may also use `aiohttp` as the HTTP backend. + +You can enable this by installing `aiohttp`: + +```sh +# install from PyPI +pip install hyperspell[aiohttp] +``` + +Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: + +```python +import os +import asyncio +from hyperspell import DefaultAioHttpClient +from hyperspell import AsyncHyperspell + + +async def main() -> None: + async with AsyncHyperspell( + api_key=os.environ.get("HYPERSPELL_TOKEN"), # This is the default and can be omitted + http_client=DefaultAioHttpClient(), + ) as client: + document_status = await client.documents.add( + text="text", + ) + print(document_status.id) + + +asyncio.run(main()) +``` + ## 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: diff --git a/pyproject.toml b/pyproject.toml index 2c96da28..c93b54d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ classifiers = [ Homepage = "https://github.com/hyperspell/python-sdk" Repository = "https://github.com/hyperspell/python-sdk" +[project.optional-dependencies] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] [tool.rye] managed = true diff --git a/requirements-dev.lock b/requirements-dev.lock index 26cd3924..86dfc66f 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 hyperspell +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 hyperspell 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 @@ -34,16 +45,23 @@ 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 hyperspell # via respx +httpx-aiohttp==0.1.6 + # via hyperspell idna==3.4 # via anyio # via httpx + # via yarl importlib-metadata==7.0.0 iniconfig==2.0.0 # via pytest @@ -51,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 @@ -65,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 hyperspell pydantic-core==2.27.1 @@ -98,11 +122,14 @@ tomli==2.0.2 typing-extensions==4.12.2 # via anyio # via hyperspell + # via multidict # via mypy # via pydantic # via pydantic-core # via pyright 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 465a1c4b..956eb06c 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 hyperspell +aiosignal==1.3.2 + # via aiohttp annotated-types==0.6.0 # via pydantic anyio==4.4.0 # via httpx # via hyperspell +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 hyperspell 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 hyperspell +httpx-aiohttp==0.1.6 # via hyperspell 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 hyperspell pydantic-core==2.27.1 @@ -41,5 +65,8 @@ sniffio==1.3.0 typing-extensions==4.12.2 # via anyio # via hyperspell + # via multidict # via pydantic # via pydantic-core +yarl==1.20.0 + # via aiohttp diff --git a/src/hyperspell/__init__.py b/src/hyperspell/__init__.py index 62f31249..3f17ad2b 100644 --- a/src/hyperspell/__init__.py +++ b/src/hyperspell/__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/hyperspell/_base_client.py b/src/hyperspell/_base_client.py index dffdfe10..1a684cc8 100644 --- a/src/hyperspell/_base_client.py +++ b/src/hyperspell/_base_client.py @@ -1289,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 @@ -1297,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): diff --git a/tests/api_resources/integrations/test_google_calendar.py b/tests/api_resources/integrations/test_google_calendar.py index 9aefb1cb..7175efac 100644 --- a/tests/api_resources/integrations/test_google_calendar.py +++ b/tests/api_resources/integrations/test_google_calendar.py @@ -44,7 +44,9 @@ def test_streaming_response_list(self, client: Hyperspell) -> None: class TestAsyncGoogleCalendar: - 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"] + ) @parametrize async def test_method_list(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/api_resources/integrations/test_web_crawler.py b/tests/api_resources/integrations/test_web_crawler.py index 7a2a2c6a..a40a698b 100644 --- a/tests/api_resources/integrations/test_web_crawler.py +++ b/tests/api_resources/integrations/test_web_crawler.py @@ -50,7 +50,9 @@ def test_streaming_response_index(self, client: Hyperspell) -> None: class TestAsyncWebCrawler: - 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"] + ) @parametrize async def test_method_index(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/api_resources/test_auth.py b/tests/api_resources/test_auth.py index e4207ba8..f4d88ecb 100644 --- a/tests/api_resources/test_auth.py +++ b/tests/api_resources/test_auth.py @@ -83,7 +83,9 @@ def test_streaming_response_user_token(self, client: Hyperspell) -> None: class TestAsyncAuth: - 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"] + ) @parametrize async def test_method_me(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/api_resources/test_collections.py b/tests/api_resources/test_collections.py index 01653ce4..700a38ac 100644 --- a/tests/api_resources/test_collections.py +++ b/tests/api_resources/test_collections.py @@ -53,7 +53,9 @@ def test_streaming_response_list(self, client: Hyperspell) -> None: class TestAsyncCollections: - 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"] + ) @parametrize async def test_method_list(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/api_resources/test_documents.py b/tests/api_resources/test_documents.py index 8f85ceba..caa44e7c 100644 --- a/tests/api_resources/test_documents.py +++ b/tests/api_resources/test_documents.py @@ -208,7 +208,9 @@ def test_streaming_response_upload(self, client: Hyperspell) -> None: class TestAsyncDocuments: - 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"] + ) @parametrize async def test_method_list(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/api_resources/test_integrations.py b/tests/api_resources/test_integrations.py index a2a32b66..25babdd3 100644 --- a/tests/api_resources/test_integrations.py +++ b/tests/api_resources/test_integrations.py @@ -57,7 +57,9 @@ def test_path_params_revoke(self, client: Hyperspell) -> None: class TestAsyncIntegrations: - 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"] + ) @parametrize async def test_method_revoke(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/api_resources/test_query.py b/tests/api_resources/test_query.py index 87650761..1005f627 100644 --- a/tests/api_resources/test_query.py +++ b/tests/api_resources/test_query.py @@ -99,7 +99,9 @@ def test_streaming_response_search(self, client: Hyperspell) -> None: class TestAsyncQuery: - 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"] + ) @parametrize async def test_method_search(self, async_client: AsyncHyperspell) -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 67487fa3..0768c982 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,10 +6,12 @@ import logging from typing import TYPE_CHECKING, Iterator, AsyncIterator +import httpx import pytest from pytest_asyncio import is_async_test -from hyperspell import Hyperspell, AsyncHyperspell +from hyperspell import Hyperspell, AsyncHyperspell, DefaultAioHttpClient +from hyperspell._utils import is_dict if TYPE_CHECKING: from _pytest.fixtures import FixtureRequest # pyright: ignore[reportPrivateImportUsage] @@ -27,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") @@ -46,11 +61,25 @@ def client(request: FixtureRequest) -> Iterator[Hyperspell]: @pytest.fixture(scope="session") async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncHyperspell]: - strict = getattr(request, "param", True) - if not isinstance(strict, bool): - raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") + 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 AsyncHyperspell( - base_url=base_url, api_key=api_key, user_id=user_id, _strict_response_validation=strict + base_url=base_url, api_key=api_key, user_id=user_id, _strict_response_validation=strict, http_client=http_client ) as client: yield client From bd59464ca0ef0c367975a52343e79b40427bfec8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 04:17:29 +0000 Subject: [PATCH 02/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 69178cfe..bff750ab 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e89c764e4c847e7825e07bdb7e927a99b29548a8864069365d50974e8d4518fe.yml openapi_spec_hash: fadd50b9f0b9675cbf5c16c318feef2f -config_hash: 32d007e1683b1936cd145cd41982ebb6 +config_hash: 6162b118c6eb4dfb0afdde0cbe2c41f3 From 9eebf628b254b4f463ea7b899dcef53af5362228 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 04:43:19 +0000 Subject: [PATCH 03/33] chore(tests): skip some failing tests on the latest python versions --- tests/test_client.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_client.py b/tests/test_client.py index 29717c98..eaa385c9 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -204,6 +204,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") @@ -1074,6 +1075,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") From a7c06e289192d67e0cd2f809345f8164dd84c220 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 20:31:47 +0000 Subject: [PATCH 04/33] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bff750ab..083feb36 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e89c764e4c847e7825e07bdb7e927a99b29548a8864069365d50974e8d4518fe.yml -openapi_spec_hash: fadd50b9f0b9675cbf5c16c318feef2f +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml +openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd config_hash: 6162b118c6eb4dfb0afdde0cbe2c41f3 From 8d7eb87bd86e9ed7d821f293842781cb7d1a0418 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:37:45 +0000 Subject: [PATCH 05/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 083feb36..a9a15153 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: 6162b118c6eb4dfb0afdde0cbe2c41f3 +config_hash: 1a1a7d17fb2f933f93364adaf4001ffa From afa72faca3393dd91b4dcc5fc9e1709a8594e31d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:38:07 +0000 Subject: [PATCH 06/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index a9a15153..9ffd4560 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: 1a1a7d17fb2f933f93364adaf4001ffa +config_hash: f9821e8eddda33d78c454dc6d3ad262b From dafd3cae408d3f5fa5509324a585637e5d0a2cd9 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:00:51 +0000 Subject: [PATCH 07/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9ffd4560..9534f5e7 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: f9821e8eddda33d78c454dc6d3ad262b +config_hash: c170585de14fe6ffe45c4b166d040400 From 79e762d91262e0b45b6e9b5d22dcfe5216ce343b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:14:26 +0000 Subject: [PATCH 08/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 9534f5e7..d35d3b65 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: c170585de14fe6ffe45c4b166d040400 +config_hash: f3d9edc67a16a1506e79bf07c3571b6b From 5ca3c2c4af3ce20d6777e501772121aa96add6ff Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:14:44 +0000 Subject: [PATCH 09/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index d35d3b65..b5ad30de 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: f3d9edc67a16a1506e79bf07c3571b6b +config_hash: 52a0dec38bb079aeaa3da46239c36f36 From 446d6012db2472511bb92562a726d2f8f538df5f Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:15:03 +0000 Subject: [PATCH 10/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index b5ad30de..7343c088 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: 52a0dec38bb079aeaa3da46239c36f36 +config_hash: e2d93b55677dc0177eb8acc7c729d205 From b0c45416d0e8f2664982bc2bcb8ea868a6e417f3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:23:46 +0000 Subject: [PATCH 11/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 7343c088..7fff409d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: e2d93b55677dc0177eb8acc7c729d205 +config_hash: 69b1946a9113d987e8fccb53eefba74a From 3a7f5f6aa00f2f5a0a24619a1c4eab02e3f49fd8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:29:17 +0000 Subject: [PATCH 12/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 7fff409d..ce43988c 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: 69b1946a9113d987e8fccb53eefba74a +config_hash: 495b960ddfab9b0c06ae9162dbabd06f From ba24d4cb72615d5edc9fe569c84e4224247f07ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:29:39 +0000 Subject: [PATCH 13/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index ce43988c..7fff409d 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd -config_hash: 495b960ddfab9b0c06ae9162dbabd06f +config_hash: 69b1946a9113d987e8fccb53eefba74a From 6ddb78b355f8e36a85fcfaf1592618bae5c67473 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 02:43:21 +0000 Subject: [PATCH 14/33] =?UTF-8?q?fix(ci):=20release-doctor=20=E2=80=94=20r?= =?UTF-8?q?eport=20correct=20token=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/check-release-environment | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/check-release-environment b/bin/check-release-environment index c9260bb3..b845b0f4 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -3,7 +3,7 @@ errors=() if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The HYPERSPELL_PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") fi lenErrors=${#errors[@]} From 991d6859286e86aa022eb83aab8ecc6e337798f4 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 08:40:31 +0000 Subject: [PATCH 15/33] chore(ci): only run for pushes and fork pull requests --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8fe4f0ac..53b82963 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: timeout-minutes: 10 name: lint runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -42,6 +43,7 @@ jobs: contents: read id-token: write runs-on: depot-ubuntu-24.04 + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 @@ -62,6 +64,7 @@ jobs: timeout-minutes: 10 name: test runs-on: ${{ github.repository == 'stainless-sdks/hyperspell-python' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} + if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From 5995a5ae4cafbe0ffb3277c3529dcba330a39327 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 28 Jun 2025 14:31:28 +0000 Subject: [PATCH 16/33] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 7fff409d..eb625f77 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ffeb902b3ec332b11850e5032c700a4bf47dd302616c93a86c08a29b2099310e.yml -openapi_spec_hash: 4f0f5628d8a3a00ee42e731d411bb4cd +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-f977a3b7918e5e00639b1a552025b9a76295eed77c90e87f4872546f71ac1e19.yml +openapi_spec_hash: e818eafdf4f72ad3d0a4a91866a09848 config_hash: 69b1946a9113d987e8fccb53eefba74a From 980b910eaa78cf84de10c0521e4ed2f142689ae0 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 02:38:16 +0000 Subject: [PATCH 17/33] fix(ci): correct conditional --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53b82963..8e475c38 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,14 +36,13 @@ jobs: run: ./scripts/lint upload: - if: github.repository == 'stainless-sdks/hyperspell-python' + if: github.repository == 'stainless-sdks/hyperspell-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 name: upload permissions: contents: read id-token: write runs-on: depot-ubuntu-24.04 - if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - uses: actions/checkout@v4 From c01d519131a2c4620c9454c4e634c2e39053b00c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 05:12:46 +0000 Subject: [PATCH 18/33] chore(ci): change upload type --- .github/workflows/ci.yml | 18 ++++++++++++++++-- scripts/utils/upload-artifact.sh | 12 +++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e475c38..99b8fcff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,10 +35,10 @@ jobs: - name: Run lints run: ./scripts/lint - upload: + build: if: github.repository == 'stainless-sdks/hyperspell-python' && (github.event_name == 'push' || github.event.pull_request.head.repo.fork) timeout-minutes: 10 - name: upload + name: build permissions: contents: read id-token: write @@ -46,6 +46,20 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install Rye + run: | + curl -sSf https://rye.astral.sh/get | bash + echo "$HOME/.rye/shims" >> $GITHUB_PATH + env: + RYE_VERSION: '0.44.0' + RYE_INSTALL_OPTION: '--yes' + + - name: Install dependencies + run: rye sync --all-features + + - name: Run build + run: rye build + - name: Get GitHub OIDC Token id: github-oidc uses: actions/github-script@v6 diff --git a/scripts/utils/upload-artifact.sh b/scripts/utils/upload-artifact.sh index 77a03527..5a2c4633 100755 --- a/scripts/utils/upload-artifact.sh +++ b/scripts/utils/upload-artifact.sh @@ -1,7 +1,9 @@ #!/usr/bin/env bash set -exuo pipefail -RESPONSE=$(curl -X POST "$URL" \ +FILENAME=$(basename dist/*.whl) + +RESPONSE=$(curl -X POST "$URL?filename=$FILENAME" \ -H "Authorization: Bearer $AUTH" \ -H "Content-Type: application/json") @@ -12,13 +14,13 @@ if [[ "$SIGNED_URL" == "null" ]]; then exit 1 fi -UPLOAD_RESPONSE=$(tar -cz . | curl -v -X PUT \ - -H "Content-Type: application/gzip" \ - --data-binary @- "$SIGNED_URL" 2>&1) +UPLOAD_RESPONSE=$(curl -v -X PUT \ + -H "Content-Type: binary/octet-stream" \ + --data-binary "@dist/$FILENAME" "$SIGNED_URL" 2>&1) if echo "$UPLOAD_RESPONSE" | grep -q "HTTP/[0-9.]* 200"; then echo -e "\033[32mUploaded build to Stainless storage.\033[0m" - echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/hyperspell-python/$SHA'\033[0m" + echo -e "\033[32mInstallation: pip install 'https://pkg.stainless.com/s/hyperspell-python/$SHA/$FILENAME'\033[0m" else echo -e "\033[31mFailed to upload artifact.\033[0m" exit 1 From f7cd6c7b1feafef059ecdf6be0bb9adbd5f28929 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 02:22:26 +0000 Subject: [PATCH 19/33] chore(internal): codegen related update --- requirements-dev.lock | 2 +- requirements.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index 86dfc66f..a0cd5182 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -56,7 +56,7 @@ httpx==0.28.1 # via httpx-aiohttp # via hyperspell # via respx -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via hyperspell idna==3.4 # via anyio diff --git a/requirements.lock b/requirements.lock index 956eb06c..0732b17f 100644 --- a/requirements.lock +++ b/requirements.lock @@ -43,7 +43,7 @@ httpcore==1.0.2 httpx==0.28.1 # via httpx-aiohttp # via hyperspell -httpx-aiohttp==0.1.6 +httpx-aiohttp==0.1.8 # via hyperspell idna==3.4 # via anyio From 1ce57814e1d392b0c6a51b75c1dd92550f9399c3 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:39:19 +0000 Subject: [PATCH 20/33] chore(internal): bump pinned h11 dep --- requirements-dev.lock | 4 ++-- requirements.lock | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-dev.lock b/requirements-dev.lock index a0cd5182..271a703f 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -48,9 +48,9 @@ filelock==3.12.4 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via httpx-aiohttp diff --git a/requirements.lock b/requirements.lock index 0732b17f..c51d6551 100644 --- a/requirements.lock +++ b/requirements.lock @@ -36,9 +36,9 @@ exceptiongroup==1.2.2 frozenlist==1.6.2 # via aiohttp # via aiosignal -h11==0.14.0 +h11==0.16.0 # via httpcore -httpcore==1.0.2 +httpcore==1.0.9 # via httpx httpx==0.28.1 # via httpx-aiohttp From b04d1e6c7d2dbd8852963ee8f08596ed332e04ec Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 02:58:29 +0000 Subject: [PATCH 21/33] chore(package): mark python 3.13 as supported --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c93b54d2..697601fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Operating System :: OS Independent", "Operating System :: POSIX", "Operating System :: MacOS", From f4e309d6a8d85f5aa0cb3fee3e41bf9c6487b5b1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 02:54:17 +0000 Subject: [PATCH 22/33] fix(parsing): correctly handle nested discriminated unions --- src/hyperspell/_models.py | 13 ++++++----- tests/test_models.py | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/hyperspell/_models.py b/src/hyperspell/_models.py index 4f214980..528d5680 100644 --- a/src/hyperspell/_models.py +++ b/src/hyperspell/_models.py @@ -2,9 +2,10 @@ import os import inspect -from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, cast +from typing import TYPE_CHECKING, Any, Type, Union, Generic, TypeVar, Callable, Optional, cast from datetime import date, datetime from typing_extensions import ( + List, Unpack, Literal, ClassVar, @@ -366,7 +367,7 @@ def _construct_field(value: object, field: FieldInfo, key: str) -> object: if type_ is None: raise RuntimeError(f"Unexpected field type is None for {key}") - return construct_type(value=value, type_=type_) + return construct_type(value=value, type_=type_, metadata=getattr(field, "metadata", None)) def is_basemodel(type_: type) -> bool: @@ -420,7 +421,7 @@ def construct_type_unchecked(*, value: object, type_: type[_T]) -> _T: return cast(_T, construct_type(value=value, type_=type_)) -def construct_type(*, value: object, type_: object) -> object: +def construct_type(*, value: object, type_: object, metadata: Optional[List[Any]] = None) -> object: """Loose coercion to the expected type with construction of nested values. If the given value does not match the expected type then it is returned as-is. @@ -438,8 +439,10 @@ def construct_type(*, value: object, type_: object) -> object: type_ = type_.__value__ # type: ignore[unreachable] # unwrap `Annotated[T, ...]` -> `T` - if is_annotated_type(type_): - meta: tuple[Any, ...] = get_args(type_)[1:] + if metadata is not None: + meta: tuple[Any, ...] = tuple(metadata) + elif is_annotated_type(type_): + meta = get_args(type_)[1:] type_ = extract_type_arg(type_, 0) else: meta = tuple() diff --git a/tests/test_models.py b/tests/test_models.py index b3a94798..cfcb602e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -889,3 +889,48 @@ class ModelB(BaseModel): ) assert isinstance(m, ModelB) + + +def test_nested_discriminated_union() -> None: + class InnerType1(BaseModel): + type: Literal["type_1"] + + class InnerModel(BaseModel): + inner_value: str + + class InnerType2(BaseModel): + type: Literal["type_2"] + some_inner_model: InnerModel + + class Type1(BaseModel): + base_type: Literal["base_type_1"] + value: Annotated[ + Union[ + InnerType1, + InnerType2, + ], + PropertyInfo(discriminator="type"), + ] + + class Type2(BaseModel): + base_type: Literal["base_type_2"] + + T = Annotated[ + Union[ + Type1, + Type2, + ], + PropertyInfo(discriminator="base_type"), + ] + + model = construct_type( + type_=T, + value={ + "base_type": "base_type_1", + "value": { + "type": "type_2", + }, + }, + ) + assert isinstance(model, Type1) + assert isinstance(model.value, InnerType2) From a1092259c98715d17ce95259420baf3c3d325db8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 11 Jul 2025 03:12:54 +0000 Subject: [PATCH 23/33] chore(readme): fix version rendering on pypi --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fc3b9310..2c823865 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Hyperspell Python API library -[![PyPI version]()](https://pypi.org/project/hyperspell/) + +[![PyPI version](https://img.shields.io/pypi/v/hyperspell.svg?label=pypi%20(stable))](https://pypi.org/project/hyperspell/) The Hyperspell Python library provides convenient access to the Hyperspell REST API from any Python 3.8+ application. The library includes type definitions for all request params and response fields, From f7a04b5a47aa9a6fc338498a34db4074824c198e Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Sat, 12 Jul 2025 02:19:25 +0000 Subject: [PATCH 24/33] fix(client): don't send Content-Type header on GET requests --- pyproject.toml | 2 +- src/hyperspell/_base_client.py | 11 +++++++++-- tests/test_client.py | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 697601fd..46197115 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ Homepage = "https://github.com/hyperspell/python-sdk" Repository = "https://github.com/hyperspell/python-sdk" [project.optional-dependencies] -aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.6"] +aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.8"] [tool.rye] managed = true diff --git a/src/hyperspell/_base_client.py b/src/hyperspell/_base_client.py index 1a684cc8..93ac9ef9 100644 --- a/src/hyperspell/_base_client.py +++ b/src/hyperspell/_base_client.py @@ -529,6 +529,15 @@ def _build_request( # work around https://github.com/encode/httpx/discussions/2880 kwargs["extensions"] = {"sni_hostname": prepared_url.host.replace("_", "-")} + is_body_allowed = options.method.lower() != "get" + + if is_body_allowed: + kwargs["json"] = json_data if is_given(json_data) else None + kwargs["files"] = files + else: + headers.pop("Content-Type", None) + kwargs.pop("data", None) + # TODO: report this error to httpx return self._client.build_request( # pyright: ignore[reportUnknownMemberType] headers=headers, @@ -540,8 +549,6 @@ def _build_request( # so that passing a `TypedDict` doesn't cause an error. # https://github.com/microsoft/pyright/issues/3526#event-6715453066 params=self.qs.stringify(cast(Mapping[str, Any], params)) if params else None, - json=json_data if is_given(json_data) else None, - files=files, **kwargs, ) diff --git a/tests/test_client.py b/tests/test_client.py index eaa385c9..aeae2cbc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -505,7 +505,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, client: Hyperspell) -> None: request = client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, @@ -1378,7 +1378,7 @@ def test_request_extra_query(self) -> None: def test_multipart_repeating_array(self, async_client: AsyncHyperspell) -> None: request = async_client._build_request( FinalRequestOptions.construct( - method="get", + method="post", url="/foo", headers={"Content-Type": "multipart/form-data; boundary=6b7ba517decee4a450543ea6ae821c82"}, json_data={"array": ["foo", "bar"]}, From e99844ee29928beeed51cd47d53e3ababe33e608 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 02:18:59 +0000 Subject: [PATCH 25/33] feat: clean up environment call outs --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 2c823865..0bcacba0 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,6 @@ pip install hyperspell[aiohttp] Then you can enable it by instantiating the client with `http_client=DefaultAioHttpClient()`: ```python -import os import asyncio from hyperspell import DefaultAioHttpClient from hyperspell import AsyncHyperspell @@ -91,7 +90,7 @@ from hyperspell import AsyncHyperspell async def main() -> None: async with AsyncHyperspell( - api_key=os.environ.get("HYPERSPELL_TOKEN"), # This is the default and can be omitted + api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: document_status = await client.documents.add( From 7248ff5db8bbc8708f8dfe8afb78d519401ee7f2 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 06:31:31 +0000 Subject: [PATCH 26/33] feat(api): api update --- .stats.yml | 4 +-- .../resources/integrations/integrations.py | 4 +-- src/hyperspell/types/query_search_params.py | 28 +++++++++++++++++++ tests/api_resources/test_query.py | 4 +++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index eb625f77..fbc2bdc0 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-f977a3b7918e5e00639b1a552025b9a76295eed77c90e87f4872546f71ac1e19.yml -openapi_spec_hash: e818eafdf4f72ad3d0a4a91866a09848 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ec52889d0859923c4b77fb5c5c0862bfde180cc5d8f4c37024b999ce4db7e702.yml +openapi_spec_hash: 1a2d88b06ccd387862127ae8cbd292a2 config_hash: 69b1946a9113d987e8fccb53eefba74a diff --git a/src/hyperspell/resources/integrations/integrations.py b/src/hyperspell/resources/integrations/integrations.py index 3bdd9529..d4e299e0 100644 --- a/src/hyperspell/resources/integrations/integrations.py +++ b/src/hyperspell/resources/integrations/integrations.py @@ -76,7 +76,7 @@ def revoke( ) -> IntegrationRevokeResponse: """ Revokes Hyperspell's access the given provider and deletes all stored - credentials. It does not delete any cached or synced data. + credentials and indexed data. Args: extra_headers: Send extra headers @@ -139,7 +139,7 @@ async def revoke( ) -> IntegrationRevokeResponse: """ Revokes Hyperspell's access the given provider and deletes all stored - credentials. It does not delete any cached or synced data. + credentials and indexed data. Args: extra_headers: Send extra headers diff --git a/src/hyperspell/types/query_search_params.py b/src/hyperspell/types/query_search_params.py index dea983c0..d519c2df 100644 --- a/src/hyperspell/types/query_search_params.py +++ b/src/hyperspell/types/query_search_params.py @@ -12,12 +12,14 @@ "QuerySearchParams", "Filter", "FilterGoogleCalendar", + "FilterGoogleMail", "FilterNotion", "FilterReddit", "FilterSlack", "FilterWebCrawler", "Options", "OptionsGoogleCalendar", + "OptionsGoogleMail", "OptionsNotion", "OptionsReddit", "OptionsSlack", @@ -105,6 +107,16 @@ class FilterGoogleCalendar(TypedDict, total=False): """ +class FilterGoogleMail(TypedDict, total=False): + label_ids: List[str] + """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']). + + Multiple labels are combined with OR logic - messages matching ANY specified + label will be returned. If empty, no label filtering is applied (searches all + accessible messages). + """ + + class FilterNotion(TypedDict, total=False): notion_page_ids: List[str] """List of Notion page IDs to search. @@ -162,6 +174,9 @@ class Filter(TypedDict, total=False): google_drive: object """Search options for Google Drive""" + google_mail: FilterGoogleMail + """Search options for Gmail""" + notion: FilterNotion """Search options for Notion""" @@ -184,6 +199,16 @@ class OptionsGoogleCalendar(TypedDict, total=False): """ +class OptionsGoogleMail(TypedDict, total=False): + label_ids: List[str] + """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']). + + Multiple labels are combined with OR logic - messages matching ANY specified + label will be returned. If empty, no label filtering is applied (searches all + accessible messages). + """ + + class OptionsNotion(TypedDict, total=False): notion_page_ids: List[str] """List of Notion page IDs to search. @@ -241,6 +266,9 @@ class Options(TypedDict, total=False): google_drive: object """Search options for Google Drive""" + google_mail: OptionsGoogleMail + """Search options for Gmail""" + notion: OptionsNotion """Search options for Notion""" diff --git a/tests/api_resources/test_query.py b/tests/api_resources/test_query.py index 1005f627..d0ef69d2 100644 --- a/tests/api_resources/test_query.py +++ b/tests/api_resources/test_query.py @@ -37,6 +37,7 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None: "collections": {}, "google_calendar": {"calendar_id": "calendar_id"}, "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, "notion": {"notion_page_ids": ["string"]}, "reddit": { "period": "hour", @@ -57,6 +58,7 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None: "collections": {}, "google_calendar": {"calendar_id": "calendar_id"}, "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, "notion": {"notion_page_ids": ["string"]}, "reddit": { "period": "hour", @@ -122,6 +124,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell "collections": {}, "google_calendar": {"calendar_id": "calendar_id"}, "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, "notion": {"notion_page_ids": ["string"]}, "reddit": { "period": "hour", @@ -142,6 +145,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell "collections": {}, "google_calendar": {"calendar_id": "calendar_id"}, "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, "notion": {"notion_page_ids": ["string"]}, "reddit": { "period": "hour", From e4edf05d2e2700fef1e70250204aee103ac54686 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:31:41 +0000 Subject: [PATCH 27/33] feat(api): api update --- .stats.yml | 6 +- README.md | 146 +--- api.md | 34 +- src/hyperspell/_client.py | 26 +- src/hyperspell/_files.py | 2 +- src/hyperspell/resources/__init__.py | 42 - src/hyperspell/resources/collections.py | 192 ----- src/hyperspell/resources/documents.py | 796 ------------------ src/hyperspell/resources/query.py | 318 ------- src/hyperspell/types/__init__.py | 10 - src/hyperspell/types/auth_me_response.py | 2 + .../types/collection_list_params.py | 14 - .../types/collection_list_response.py | 13 - src/hyperspell/types/document.py | 90 -- src/hyperspell/types/document_add_params.py | 38 - src/hyperspell/types/document_list_params.py | 68 -- src/hyperspell/types/document_status.py | 64 -- .../types/document_status_response.py | 13 - .../types/document_upload_params.py | 18 - .../web_crawler_index_response.py | 1 + src/hyperspell/types/query_search_params.py | 282 ------- src/hyperspell/types/query_search_response.py | 22 - tests/api_resources/test_collections.py | 91 -- tests/api_resources/test_documents.py | 396 --------- tests/api_resources/test_query.py | 187 ---- tests/test_client.py | 48 +- 26 files changed, 55 insertions(+), 2864 deletions(-) delete mode 100644 src/hyperspell/resources/collections.py delete mode 100644 src/hyperspell/resources/documents.py delete mode 100644 src/hyperspell/resources/query.py delete mode 100644 src/hyperspell/types/collection_list_params.py delete mode 100644 src/hyperspell/types/collection_list_response.py delete mode 100644 src/hyperspell/types/document.py delete mode 100644 src/hyperspell/types/document_add_params.py delete mode 100644 src/hyperspell/types/document_list_params.py delete mode 100644 src/hyperspell/types/document_status.py delete mode 100644 src/hyperspell/types/document_status_response.py delete mode 100644 src/hyperspell/types/document_upload_params.py delete mode 100644 src/hyperspell/types/query_search_params.py delete mode 100644 src/hyperspell/types/query_search_response.py delete mode 100644 tests/api_resources/test_collections.py delete mode 100644 tests/api_resources/test_documents.py delete mode 100644 tests/api_resources/test_query.py diff --git a/.stats.yml b/.stats.yml index fbc2bdc0..bff33e9b 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 12 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-ec52889d0859923c4b77fb5c5c0862bfde180cc5d8f4c37024b999ce4db7e702.yml -openapi_spec_hash: 1a2d88b06ccd387862127ae8cbd292a2 +configured_endpoints: 5 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e69401a4b445430dd5b4272d4d7084a88fe9082d96931fdd91f6a12b1c851fa3.yml +openapi_spec_hash: ef23d82a9c899f2dfdcdd537890a22e6 config_hash: 69b1946a9113d987e8fccb53eefba74a diff --git a/README.md b/README.md index 0bcacba0..83dfd642 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ client = Hyperspell( api_key=os.environ.get("HYPERSPELL_TOKEN"), # This is the default and can be omitted ) -document_status = client.documents.add( - text="text", +response = client.integrations.revoke( + "provider", ) -print(document_status.id) +print(response.message) ``` While you can provide an `api_key` keyword argument, @@ -58,10 +58,10 @@ client = AsyncHyperspell( async def main() -> None: - document_status = await client.documents.add( - text="text", + response = await client.integrations.revoke( + "provider", ) - print(document_status.id) + print(response.message) asyncio.run(main()) @@ -93,10 +93,10 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - document_status = await client.documents.add( - text="text", + response = await client.integrations.revoke( + "provider", ) - print(document_status.id) + print(response.message) asyncio.run(main()) @@ -111,110 +111,6 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. -## Pagination - -List methods in the Hyperspell API are paginated. - -This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: - -```python -from hyperspell import Hyperspell - -client = Hyperspell() - -all_documents = [] -# Automatically fetches more pages as needed. -for document in client.documents.list( - collection="REPLACE_ME", -): - # Do something with document here - all_documents.append(document) -print(all_documents) -``` - -Or, asynchronously: - -```python -import asyncio -from hyperspell import AsyncHyperspell - -client = AsyncHyperspell() - - -async def main() -> None: - all_documents = [] - # Iterate through items across all pages, issuing requests as needed. - async for document in client.documents.list( - collection="REPLACE_ME", - ): - all_documents.append(document) - print(all_documents) - - -asyncio.run(main()) -``` - -Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: - -```python -first_page = await client.documents.list( - collection="REPLACE_ME", -) -if first_page.has_next_page(): - print(f"will fetch next page using these details: {first_page.next_page_info()}") - next_page = await first_page.get_next_page() - print(f"number of items we just fetched: {len(next_page.items)}") - -# Remove `await` for non-async usage. -``` - -Or just work directly with the returned data: - -```python -first_page = await client.documents.list( - collection="REPLACE_ME", -) - -print(f"next page cursor: {first_page.next_cursor}") # => "next page cursor: ..." -for document in first_page.items: - print(document.resource_id) - -# Remove `await` for non-async usage. -``` - -## Nested params - -Nested parameters are dictionaries, typed using `TypedDict`, for example: - -```python -from hyperspell import Hyperspell - -client = Hyperspell() - -response = client.query.search( - query="query", - filter={}, -) -print(response.filter) -``` - -## File uploads - -Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. - -```python -from pathlib import Path -from hyperspell import Hyperspell - -client = Hyperspell() - -client.documents.upload( - file=Path("/path/to/file"), -) -``` - -The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. - ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `hyperspell.APIConnectionError` is raised. @@ -231,8 +127,8 @@ from hyperspell import Hyperspell client = Hyperspell() try: - client.documents.add( - text="text", + client.integrations.revoke( + "provider", ) except hyperspell.APIConnectionError as e: print("The server could not be reached") @@ -276,8 +172,8 @@ client = Hyperspell( ) # Or, configure per-request: -client.with_options(max_retries=5).documents.add( - text="text", +client.with_options(max_retries=5).integrations.revoke( + "provider", ) ``` @@ -301,8 +197,8 @@ client = Hyperspell( ) # Override per-request: -client.with_options(timeout=5.0).documents.add( - text="text", +client.with_options(timeout=5.0).integrations.revoke( + "provider", ) ``` @@ -344,13 +240,13 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from hyperspell import Hyperspell client = Hyperspell() -response = client.documents.with_raw_response.add( - text="text", +response = client.integrations.with_raw_response.revoke( + "provider", ) print(response.headers.get('X-My-Header')) -document = response.parse() # get the object that `documents.add()` would have returned -print(document.id) +integration = response.parse() # get the object that `integrations.revoke()` would have returned +print(integration.message) ``` These methods return an [`APIResponse`](https://github.com/hyperspell/python-sdk/tree/main/src/hyperspell/_response.py) object. @@ -364,8 +260,8 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.documents.with_streaming_response.add( - text="text", +with client.integrations.with_streaming_response.revoke( + "provider", ) as response: print(response.headers.get("X-My-Header")) diff --git a/api.md b/api.md index c8fcbbc2..1fdf4281 100644 --- a/api.md +++ b/api.md @@ -39,41 +39,9 @@ Methods: Types: ```python -from hyperspell.types import Document, DocumentStatus, DocumentStatusResponse +from hyperspell.types import Document, DocumentStatus ``` -Methods: - -- client.documents.list(\*\*params) -> SyncCursorPage[Document] -- client.documents.add(\*\*params) -> DocumentStatus -- client.documents.get(resource_id, \*, source) -> Document -- client.documents.status() -> DocumentStatusResponse -- client.documents.upload(\*\*params) -> DocumentStatus - -# Collections - -Types: - -```python -from hyperspell.types import CollectionListResponse -``` - -Methods: - -- client.collections.list(\*\*params) -> SyncCursorPage[CollectionListResponse] - -# Query - -Types: - -```python -from hyperspell.types import QuerySearchResponse -``` - -Methods: - -- client.query.search(\*\*params) -> QuerySearchResponse - # Auth Types: diff --git a/src/hyperspell/_client.py b/src/hyperspell/_client.py index 1fa37b21..e5450ae1 100644 --- a/src/hyperspell/_client.py +++ b/src/hyperspell/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import auth, query, documents, collections +from .resources import auth from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, HyperspellError from ._base_client import ( @@ -45,9 +45,6 @@ class Hyperspell(SyncAPIClient): integrations: integrations.IntegrationsResource - documents: documents.DocumentsResource - collections: collections.CollectionsResource - query: query.QueryResource auth: auth.AuthResource with_raw_response: HyperspellWithRawResponse with_streaming_response: HyperspellWithStreamedResponse @@ -111,9 +108,6 @@ def __init__( ) self.integrations = integrations.IntegrationsResource(self) - self.documents = documents.DocumentsResource(self) - self.collections = collections.CollectionsResource(self) - self.query = query.QueryResource(self) self.auth = auth.AuthResource(self) self.with_raw_response = HyperspellWithRawResponse(self) self.with_streaming_response = HyperspellWithStreamedResponse(self) @@ -238,9 +232,6 @@ def _make_status_error( class AsyncHyperspell(AsyncAPIClient): integrations: integrations.AsyncIntegrationsResource - documents: documents.AsyncDocumentsResource - collections: collections.AsyncCollectionsResource - query: query.AsyncQueryResource auth: auth.AsyncAuthResource with_raw_response: AsyncHyperspellWithRawResponse with_streaming_response: AsyncHyperspellWithStreamedResponse @@ -304,9 +295,6 @@ def __init__( ) self.integrations = integrations.AsyncIntegrationsResource(self) - self.documents = documents.AsyncDocumentsResource(self) - self.collections = collections.AsyncCollectionsResource(self) - self.query = query.AsyncQueryResource(self) self.auth = auth.AsyncAuthResource(self) self.with_raw_response = AsyncHyperspellWithRawResponse(self) self.with_streaming_response = AsyncHyperspellWithStreamedResponse(self) @@ -432,36 +420,24 @@ def _make_status_error( class HyperspellWithRawResponse: def __init__(self, client: Hyperspell) -> None: self.integrations = integrations.IntegrationsResourceWithRawResponse(client.integrations) - self.documents = documents.DocumentsResourceWithRawResponse(client.documents) - self.collections = collections.CollectionsResourceWithRawResponse(client.collections) - self.query = query.QueryResourceWithRawResponse(client.query) self.auth = auth.AuthResourceWithRawResponse(client.auth) class AsyncHyperspellWithRawResponse: def __init__(self, client: AsyncHyperspell) -> None: self.integrations = integrations.AsyncIntegrationsResourceWithRawResponse(client.integrations) - self.documents = documents.AsyncDocumentsResourceWithRawResponse(client.documents) - self.collections = collections.AsyncCollectionsResourceWithRawResponse(client.collections) - self.query = query.AsyncQueryResourceWithRawResponse(client.query) self.auth = auth.AsyncAuthResourceWithRawResponse(client.auth) class HyperspellWithStreamedResponse: def __init__(self, client: Hyperspell) -> None: self.integrations = integrations.IntegrationsResourceWithStreamingResponse(client.integrations) - self.documents = documents.DocumentsResourceWithStreamingResponse(client.documents) - self.collections = collections.CollectionsResourceWithStreamingResponse(client.collections) - self.query = query.QueryResourceWithStreamingResponse(client.query) self.auth = auth.AuthResourceWithStreamingResponse(client.auth) class AsyncHyperspellWithStreamedResponse: def __init__(self, client: AsyncHyperspell) -> None: self.integrations = integrations.AsyncIntegrationsResourceWithStreamingResponse(client.integrations) - self.documents = documents.AsyncDocumentsResourceWithStreamingResponse(client.documents) - self.collections = collections.AsyncCollectionsResourceWithStreamingResponse(client.collections) - self.query = query.AsyncQueryResourceWithStreamingResponse(client.query) self.auth = auth.AsyncAuthResourceWithStreamingResponse(client.auth) diff --git a/src/hyperspell/_files.py b/src/hyperspell/_files.py index 1d0a3358..715cc207 100644 --- a/src/hyperspell/_files.py +++ b/src/hyperspell/_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/hyperspell/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/hyperspell/resources/__init__.py b/src/hyperspell/resources/__init__.py index 35893fbf..de0f600d 100644 --- a/src/hyperspell/resources/__init__.py +++ b/src/hyperspell/resources/__init__.py @@ -8,30 +8,6 @@ AuthResourceWithStreamingResponse, AsyncAuthResourceWithStreamingResponse, ) -from .query import ( - QueryResource, - AsyncQueryResource, - QueryResourceWithRawResponse, - AsyncQueryResourceWithRawResponse, - QueryResourceWithStreamingResponse, - AsyncQueryResourceWithStreamingResponse, -) -from .documents import ( - DocumentsResource, - AsyncDocumentsResource, - DocumentsResourceWithRawResponse, - AsyncDocumentsResourceWithRawResponse, - DocumentsResourceWithStreamingResponse, - AsyncDocumentsResourceWithStreamingResponse, -) -from .collections import ( - CollectionsResource, - AsyncCollectionsResource, - CollectionsResourceWithRawResponse, - AsyncCollectionsResourceWithRawResponse, - CollectionsResourceWithStreamingResponse, - AsyncCollectionsResourceWithStreamingResponse, -) from .integrations import ( IntegrationsResource, AsyncIntegrationsResource, @@ -48,24 +24,6 @@ "AsyncIntegrationsResourceWithRawResponse", "IntegrationsResourceWithStreamingResponse", "AsyncIntegrationsResourceWithStreamingResponse", - "DocumentsResource", - "AsyncDocumentsResource", - "DocumentsResourceWithRawResponse", - "AsyncDocumentsResourceWithRawResponse", - "DocumentsResourceWithStreamingResponse", - "AsyncDocumentsResourceWithStreamingResponse", - "CollectionsResource", - "AsyncCollectionsResource", - "CollectionsResourceWithRawResponse", - "AsyncCollectionsResourceWithRawResponse", - "CollectionsResourceWithStreamingResponse", - "AsyncCollectionsResourceWithStreamingResponse", - "QueryResource", - "AsyncQueryResource", - "QueryResourceWithRawResponse", - "AsyncQueryResourceWithRawResponse", - "QueryResourceWithStreamingResponse", - "AsyncQueryResourceWithStreamingResponse", "AuthResource", "AsyncAuthResource", "AuthResourceWithRawResponse", diff --git a/src/hyperspell/resources/collections.py b/src/hyperspell/resources/collections.py deleted file mode 100644 index e0daa155..00000000 --- a/src/hyperspell/resources/collections.py +++ /dev/null @@ -1,192 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional - -import httpx - -from ..types import collection_list_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven -from .._utils import 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 ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import AsyncPaginator, make_request_options -from ..types.collection_list_response import CollectionListResponse - -__all__ = ["CollectionsResource", "AsyncCollectionsResource"] - - -class CollectionsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> CollectionsResourceWithRawResponse: - """ - 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers - """ - return CollectionsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> CollectionsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response - """ - return CollectionsResourceWithStreamingResponse(self) - - def list( - self, - *, - cursor: Optional[str] | NotGiven = NOT_GIVEN, - size: int | 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, - ) -> SyncCursorPage[CollectionListResponse]: - """ - This endpoint lists all collections, and how many documents are in each - collection. All documents that do not have a collection assigned are in the - `null` collection. - - 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_api_list( - "/collections/list", - page=SyncCursorPage[CollectionListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "cursor": cursor, - "size": size, - }, - collection_list_params.CollectionListParams, - ), - ), - model=CollectionListResponse, - ) - - -class AsyncCollectionsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncCollectionsResourceWithRawResponse: - """ - 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncCollectionsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncCollectionsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response - """ - return AsyncCollectionsResourceWithStreamingResponse(self) - - def list( - self, - *, - cursor: Optional[str] | NotGiven = NOT_GIVEN, - size: int | 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, - ) -> AsyncPaginator[CollectionListResponse, AsyncCursorPage[CollectionListResponse]]: - """ - This endpoint lists all collections, and how many documents are in each - collection. All documents that do not have a collection assigned are in the - `null` collection. - - 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_api_list( - "/collections/list", - page=AsyncCursorPage[CollectionListResponse], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "cursor": cursor, - "size": size, - }, - collection_list_params.CollectionListParams, - ), - ), - model=CollectionListResponse, - ) - - -class CollectionsResourceWithRawResponse: - def __init__(self, collections: CollectionsResource) -> None: - self._collections = collections - - self.list = to_raw_response_wrapper( - collections.list, - ) - - -class AsyncCollectionsResourceWithRawResponse: - def __init__(self, collections: AsyncCollectionsResource) -> None: - self._collections = collections - - self.list = async_to_raw_response_wrapper( - collections.list, - ) - - -class CollectionsResourceWithStreamingResponse: - def __init__(self, collections: CollectionsResource) -> None: - self._collections = collections - - self.list = to_streamed_response_wrapper( - collections.list, - ) - - -class AsyncCollectionsResourceWithStreamingResponse: - def __init__(self, collections: AsyncCollectionsResource) -> None: - self._collections = collections - - self.list = async_to_streamed_response_wrapper( - collections.list, - ) diff --git a/src/hyperspell/resources/documents.py b/src/hyperspell/resources/documents.py deleted file mode 100644 index 5d629445..00000000 --- a/src/hyperspell/resources/documents.py +++ /dev/null @@ -1,796 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union, Mapping, Optional, cast -from datetime import datetime -from typing_extensions import Literal - -import httpx - -from ..types import document_add_params, document_list_params, document_upload_params -from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes -from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform -from .._compat import cached_property -from .._resource import SyncAPIResource, AsyncAPIResource -from .._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..pagination import SyncCursorPage, AsyncCursorPage -from .._base_client import AsyncPaginator, make_request_options -from ..types.document import Document -from ..types.document_status import DocumentStatus -from ..types.document_status_response import DocumentStatusResponse - -__all__ = ["DocumentsResource", "AsyncDocumentsResource"] - - -class DocumentsResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> DocumentsResourceWithRawResponse: - """ - 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers - """ - return DocumentsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> DocumentsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response - """ - return DocumentsResourceWithStreamingResponse(self) - - def list( - self, - *, - collection: Optional[str] | NotGiven = NOT_GIVEN, - cursor: Optional[str] | NotGiven = NOT_GIVEN, - size: int | NotGiven = NOT_GIVEN, - source: Optional[ - Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - ] - | 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, - ) -> SyncCursorPage[Document]: - """This endpoint allows you to paginate through all documents in the index. - - You can - filter the documents by title, date, metadata, etc. - - Args: - collection: Filter documents by collection. - - source: Filter documents by source. - - 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_api_list( - "/documents/list", - page=SyncCursorPage[Document], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "collection": collection, - "cursor": cursor, - "size": size, - "source": source, - }, - document_list_params.DocumentListParams, - ), - ), - model=Document, - ) - - def add( - self, - *, - text: str, - collection: Optional[str] | NotGiven = NOT_GIVEN, - date: Union[str, datetime] | NotGiven = NOT_GIVEN, - resource_id: str | NotGiven = NOT_GIVEN, - title: Optional[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, - ) -> DocumentStatus: - """Adds an arbitrary document to the index. - - This can be any text, email, call - transcript, etc. The document will be processed and made available for querying - once the processing is complete. - - Args: - text: Full text of the document. - - collection: The collection to add the document to for easier retrieval. - - date: Date of the document. Depending on the document, this could be the creation date - or date the document was last updated (eg. for a chat transcript, this would be - the date of the last message). This helps the ranking algorithm and allows you - to filter by date range. - - resource_id: The resource ID to add the document to. If not provided, a new resource ID will - be generated. If provided, the document will be updated if it already exists. - - title: Title of the document. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/documents/add", - body=maybe_transform( - { - "text": text, - "collection": collection, - "date": date, - "resource_id": resource_id, - "title": title, - }, - document_add_params.DocumentAddParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DocumentStatus, - ) - - def get( - self, - resource_id: str, - *, - source: Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Document: - """ - Retrieves a document by provider and resource_id. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not source: - raise ValueError(f"Expected a non-empty value for `source` but received {source!r}") - if not resource_id: - raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}") - return self._get( - f"/documents/get/{source}/{resource_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Document, - ) - - def status( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> DocumentStatusResponse: - """ - This endpoint shows the indexing progress of documents, both by provider and - total. - """ - return self._get( - "/documents/status", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DocumentStatusResponse, - ) - - def upload( - self, - *, - file: FileTypes, - collection: Optional[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, - ) -> DocumentStatus: - """This endpoint will upload a file to the index and return a document ID. - - The file - will be processed in the background and the document will be available for - querying once the processing is complete. You can use the `document_id` to query - the document later, and check the status of the document. - - Args: - file: The file to ingest. - - collection: The collection to add the document to. - - 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, - "collection": collection, - } - ) - 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( - "/documents/upload", - body=maybe_transform(body, document_upload_params.DocumentUploadParams), - files=files, - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DocumentStatus, - ) - - -class AsyncDocumentsResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncDocumentsResourceWithRawResponse: - """ - 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncDocumentsResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncDocumentsResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response - """ - return AsyncDocumentsResourceWithStreamingResponse(self) - - def list( - self, - *, - collection: Optional[str] | NotGiven = NOT_GIVEN, - cursor: Optional[str] | NotGiven = NOT_GIVEN, - size: int | NotGiven = NOT_GIVEN, - source: Optional[ - Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - ] - | 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, - ) -> AsyncPaginator[Document, AsyncCursorPage[Document]]: - """This endpoint allows you to paginate through all documents in the index. - - You can - filter the documents by title, date, metadata, etc. - - Args: - collection: Filter documents by collection. - - source: Filter documents by source. - - 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_api_list( - "/documents/list", - page=AsyncCursorPage[Document], - options=make_request_options( - extra_headers=extra_headers, - extra_query=extra_query, - extra_body=extra_body, - timeout=timeout, - query=maybe_transform( - { - "collection": collection, - "cursor": cursor, - "size": size, - "source": source, - }, - document_list_params.DocumentListParams, - ), - ), - model=Document, - ) - - async def add( - self, - *, - text: str, - collection: Optional[str] | NotGiven = NOT_GIVEN, - date: Union[str, datetime] | NotGiven = NOT_GIVEN, - resource_id: str | NotGiven = NOT_GIVEN, - title: Optional[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, - ) -> DocumentStatus: - """Adds an arbitrary document to the index. - - This can be any text, email, call - transcript, etc. The document will be processed and made available for querying - once the processing is complete. - - Args: - text: Full text of the document. - - collection: The collection to add the document to for easier retrieval. - - date: Date of the document. Depending on the document, this could be the creation date - or date the document was last updated (eg. for a chat transcript, this would be - the date of the last message). This helps the ranking algorithm and allows you - to filter by date range. - - resource_id: The resource ID to add the document to. If not provided, a new resource ID will - be generated. If provided, the document will be updated if it already exists. - - title: Title of the document. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/documents/add", - body=await async_maybe_transform( - { - "text": text, - "collection": collection, - "date": date, - "resource_id": resource_id, - "title": title, - }, - document_add_params.DocumentAddParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DocumentStatus, - ) - - async def get( - self, - resource_id: str, - *, - source: Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ], - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Document: - """ - Retrieves a document by provider and resource_id. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not source: - raise ValueError(f"Expected a non-empty value for `source` but received {source!r}") - if not resource_id: - raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}") - return await self._get( - f"/documents/get/{source}/{resource_id}", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=Document, - ) - - async def status( - self, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> DocumentStatusResponse: - """ - This endpoint shows the indexing progress of documents, both by provider and - total. - """ - return await self._get( - "/documents/status", - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DocumentStatusResponse, - ) - - async def upload( - self, - *, - file: FileTypes, - collection: Optional[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, - ) -> DocumentStatus: - """This endpoint will upload a file to the index and return a document ID. - - The file - will be processed in the background and the document will be available for - querying once the processing is complete. You can use the `document_id` to query - the document later, and check the status of the document. - - Args: - file: The file to ingest. - - collection: The collection to add the document to. - - 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, - "collection": collection, - } - ) - 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( - "/documents/upload", - body=await async_maybe_transform(body, document_upload_params.DocumentUploadParams), - files=files, - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=DocumentStatus, - ) - - -class DocumentsResourceWithRawResponse: - def __init__(self, documents: DocumentsResource) -> None: - self._documents = documents - - self.list = to_raw_response_wrapper( - documents.list, - ) - self.add = to_raw_response_wrapper( - documents.add, - ) - self.get = to_raw_response_wrapper( - documents.get, - ) - self.status = to_raw_response_wrapper( - documents.status, - ) - self.upload = to_raw_response_wrapper( - documents.upload, - ) - - -class AsyncDocumentsResourceWithRawResponse: - def __init__(self, documents: AsyncDocumentsResource) -> None: - self._documents = documents - - self.list = async_to_raw_response_wrapper( - documents.list, - ) - self.add = async_to_raw_response_wrapper( - documents.add, - ) - self.get = async_to_raw_response_wrapper( - documents.get, - ) - self.status = async_to_raw_response_wrapper( - documents.status, - ) - self.upload = async_to_raw_response_wrapper( - documents.upload, - ) - - -class DocumentsResourceWithStreamingResponse: - def __init__(self, documents: DocumentsResource) -> None: - self._documents = documents - - self.list = to_streamed_response_wrapper( - documents.list, - ) - self.add = to_streamed_response_wrapper( - documents.add, - ) - self.get = to_streamed_response_wrapper( - documents.get, - ) - self.status = to_streamed_response_wrapper( - documents.status, - ) - self.upload = to_streamed_response_wrapper( - documents.upload, - ) - - -class AsyncDocumentsResourceWithStreamingResponse: - def __init__(self, documents: AsyncDocumentsResource) -> None: - self._documents = documents - - self.list = async_to_streamed_response_wrapper( - documents.list, - ) - self.add = async_to_streamed_response_wrapper( - documents.add, - ) - self.get = async_to_streamed_response_wrapper( - documents.get, - ) - self.status = async_to_streamed_response_wrapper( - documents.status, - ) - self.upload = async_to_streamed_response_wrapper( - documents.upload, - ) diff --git a/src/hyperspell/resources/query.py b/src/hyperspell/resources/query.py deleted file mode 100644 index 3b8d4533..00000000 --- a/src/hyperspell/resources/query.py +++ /dev/null @@ -1,318 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Optional -from typing_extensions import Literal - -import httpx - -from ..types import query_search_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.query_search_response import QuerySearchResponse - -__all__ = ["QueryResource", "AsyncQueryResource"] - - -class QueryResource(SyncAPIResource): - @cached_property - def with_raw_response(self) -> QueryResourceWithRawResponse: - """ - 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers - """ - return QueryResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> QueryResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response - """ - return QueryResourceWithStreamingResponse(self) - - def search( - self, - *, - query: str, - answer: bool | NotGiven = NOT_GIVEN, - filter: Optional[query_search_params.Filter] | NotGiven = NOT_GIVEN, - max_results: int | NotGiven = NOT_GIVEN, - options: query_search_params.Options | NotGiven = NOT_GIVEN, - sources: List[ - Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - ] - | 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, - ) -> QuerySearchResponse: - """ - Retrieves documents matching the query. - - Args: - query: Query to run. - - answer: If true, the query will be answered along with matching source documents. - - filter: DEPRECATED: Use options instead. This field will be removed in a future version. - - max_results: Maximum number of results to return. - - options: Search options for the query. - - sources: Only query documents from these sources. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return self._post( - "/query", - body=maybe_transform( - { - "query": query, - "answer": answer, - "filter": filter, - "max_results": max_results, - "options": options, - "sources": sources, - }, - query_search_params.QuerySearchParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=QuerySearchResponse, - ) - - -class AsyncQueryResource(AsyncAPIResource): - @cached_property - def with_raw_response(self) -> AsyncQueryResourceWithRawResponse: - """ - 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers - """ - return AsyncQueryResourceWithRawResponse(self) - - @cached_property - def with_streaming_response(self) -> AsyncQueryResourceWithStreamingResponse: - """ - An alternative to `.with_raw_response` that doesn't eagerly read the response body. - - For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response - """ - return AsyncQueryResourceWithStreamingResponse(self) - - async def search( - self, - *, - query: str, - answer: bool | NotGiven = NOT_GIVEN, - filter: Optional[query_search_params.Filter] | NotGiven = NOT_GIVEN, - max_results: int | NotGiven = NOT_GIVEN, - options: query_search_params.Options | NotGiven = NOT_GIVEN, - sources: List[ - Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - ] - | 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, - ) -> QuerySearchResponse: - """ - Retrieves documents matching the query. - - Args: - query: Query to run. - - answer: If true, the query will be answered along with matching source documents. - - filter: DEPRECATED: Use options instead. This field will be removed in a future version. - - max_results: Maximum number of results to return. - - options: Search options for the query. - - sources: Only query documents from these sources. - - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - return await self._post( - "/query", - body=await async_maybe_transform( - { - "query": query, - "answer": answer, - "filter": filter, - "max_results": max_results, - "options": options, - "sources": sources, - }, - query_search_params.QuerySearchParams, - ), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=QuerySearchResponse, - ) - - -class QueryResourceWithRawResponse: - def __init__(self, query: QueryResource) -> None: - self._query = query - - self.search = to_raw_response_wrapper( - query.search, - ) - - -class AsyncQueryResourceWithRawResponse: - def __init__(self, query: AsyncQueryResource) -> None: - self._query = query - - self.search = async_to_raw_response_wrapper( - query.search, - ) - - -class QueryResourceWithStreamingResponse: - def __init__(self, query: QueryResource) -> None: - self._query = query - - self.search = to_streamed_response_wrapper( - query.search, - ) - - -class AsyncQueryResourceWithStreamingResponse: - def __init__(self, query: AsyncQueryResource) -> None: - self._query = query - - self.search = async_to_streamed_response_wrapper( - query.search, - ) diff --git a/src/hyperspell/types/__init__.py b/src/hyperspell/types/__init__.py index 4027566b..06b0608b 100644 --- a/src/hyperspell/types/__init__.py +++ b/src/hyperspell/types/__init__.py @@ -3,16 +3,6 @@ from __future__ import annotations from .token import Token as Token -from .document import Document as Document -from .document_status import DocumentStatus as DocumentStatus from .auth_me_response import AuthMeResponse as AuthMeResponse -from .document_add_params import DocumentAddParams as DocumentAddParams -from .query_search_params import QuerySearchParams as QuerySearchParams -from .document_list_params import DocumentListParams as DocumentListParams -from .query_search_response import QuerySearchResponse as QuerySearchResponse from .auth_user_token_params import AuthUserTokenParams as AuthUserTokenParams -from .collection_list_params import CollectionListParams as CollectionListParams -from .document_upload_params import DocumentUploadParams as DocumentUploadParams -from .collection_list_response import CollectionListResponse as CollectionListResponse -from .document_status_response import DocumentStatusResponse as DocumentStatusResponse from .integration_revoke_response import IntegrationRevokeResponse as IntegrationRevokeResponse diff --git a/src/hyperspell/types/auth_me_response.py b/src/hyperspell/types/auth_me_response.py index 10603ffe..64acb118 100644 --- a/src/hyperspell/types/auth_me_response.py +++ b/src/hyperspell/types/auth_me_response.py @@ -19,6 +19,7 @@ class AuthMeResponse(BaseModel): available_integrations: List[ Literal[ "collections", + "vault", "web_crawler", "notion", "slack", @@ -70,6 +71,7 @@ class AuthMeResponse(BaseModel): installed_integrations: List[ Literal[ "collections", + "vault", "web_crawler", "notion", "slack", diff --git a/src/hyperspell/types/collection_list_params.py b/src/hyperspell/types/collection_list_params.py deleted file mode 100644 index a6f96181..00000000 --- a/src/hyperspell/types/collection_list_params.py +++ /dev/null @@ -1,14 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import TypedDict - -__all__ = ["CollectionListParams"] - - -class CollectionListParams(TypedDict, total=False): - cursor: Optional[str] - - size: int diff --git a/src/hyperspell/types/collection_list_response.py b/src/hyperspell/types/collection_list_response.py deleted file mode 100644 index 6aebcc93..00000000 --- a/src/hyperspell/types/collection_list_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Optional - -from .._models import BaseModel - -__all__ = ["CollectionListResponse"] - - -class CollectionListResponse(BaseModel): - collection: Optional[str] = None - - document_count: int diff --git a/src/hyperspell/types/document.py b/src/hyperspell/types/document.py deleted file mode 100644 index 69eab7b2..00000000 --- a/src/hyperspell/types/document.py +++ /dev/null @@ -1,90 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import TYPE_CHECKING, List, Optional -from datetime import datetime -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["Document", "Metadata", "MetadataEvent"] - - -class MetadataEvent(BaseModel): - message: str - - type: Literal["error", "warning", "info", "success"] - - time: Optional[datetime] = None - - -class Metadata(BaseModel): - events: Optional[List[MetadataEvent]] = None - - indexed_at: Optional[datetime] = None - - last_modified: Optional[datetime] = None - - status: Optional[Literal["pending", "processing", "completed", "failed"]] = None - - if TYPE_CHECKING: - # Stub to indicate that arbitrary properties are accepted. - # To access properties that are not valid identifiers you can use `getattr`, e.g. - # `getattr(obj, '$type')` - def __getattr__(self, attr: str) -> object: ... - - -class Document(BaseModel): - resource_id: str - - source: Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - - metadata: Optional[Metadata] = None - - score: Optional[float] = None - """The relevance of the resource to the query""" diff --git a/src/hyperspell/types/document_add_params.py b/src/hyperspell/types/document_add_params.py deleted file mode 100644 index 8ebf1534..00000000 --- a/src/hyperspell/types/document_add_params.py +++ /dev/null @@ -1,38 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Union, Optional -from datetime import datetime -from typing_extensions import Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = ["DocumentAddParams"] - - -class DocumentAddParams(TypedDict, total=False): - text: Required[str] - """Full text of the document.""" - - collection: Optional[str] - """The collection to add the document to for easier retrieval.""" - - date: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] - """Date of the document. - - Depending on the document, this could be the creation date or date the document - was last updated (eg. for a chat transcript, this would be the date of the last - message). This helps the ranking algorithm and allows you to filter by date - range. - """ - - resource_id: str - """The resource ID to add the document to. - - If not provided, a new resource ID will be generated. If provided, the document - will be updated if it already exists. - """ - - title: Optional[str] - """Title of the document.""" diff --git a/src/hyperspell/types/document_list_params.py b/src/hyperspell/types/document_list_params.py deleted file mode 100644 index 53440ca9..00000000 --- a/src/hyperspell/types/document_list_params.py +++ /dev/null @@ -1,68 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Literal, TypedDict - -__all__ = ["DocumentListParams"] - - -class DocumentListParams(TypedDict, total=False): - collection: Optional[str] - """Filter documents by collection.""" - - cursor: Optional[str] - - size: int - - source: Optional[ - Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - ] - """Filter documents by source.""" diff --git a/src/hyperspell/types/document_status.py b/src/hyperspell/types/document_status.py deleted file mode 100644 index 7ae05f1a..00000000 --- a/src/hyperspell/types/document_status.py +++ /dev/null @@ -1,64 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing_extensions import Literal - -from .._models import BaseModel - -__all__ = ["DocumentStatus"] - - -class DocumentStatus(BaseModel): - id: int - """Deprecated: refer to documents by source and resource_id instead""" - - resource_id: str - - source: Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - - status: Literal["pending", "processing", "completed", "failed"] diff --git a/src/hyperspell/types/document_status_response.py b/src/hyperspell/types/document_status_response.py deleted file mode 100644 index 022be1c7..00000000 --- a/src/hyperspell/types/document_status_response.py +++ /dev/null @@ -1,13 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict - -from .._models import BaseModel - -__all__ = ["DocumentStatusResponse"] - - -class DocumentStatusResponse(BaseModel): - providers: Dict[str, Dict[str, int]] - - total: Dict[str, int] diff --git a/src/hyperspell/types/document_upload_params.py b/src/hyperspell/types/document_upload_params.py deleted file mode 100644 index 84300e23..00000000 --- a/src/hyperspell/types/document_upload_params.py +++ /dev/null @@ -1,18 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import Optional -from typing_extensions import Required, TypedDict - -from .._types import FileTypes - -__all__ = ["DocumentUploadParams"] - - -class DocumentUploadParams(TypedDict, total=False): - file: Required[FileTypes] - """The file to ingest.""" - - collection: Optional[str] - """The collection to add the document to.""" diff --git a/src/hyperspell/types/integrations/web_crawler_index_response.py b/src/hyperspell/types/integrations/web_crawler_index_response.py index e5af96ce..deddf6e7 100644 --- a/src/hyperspell/types/integrations/web_crawler_index_response.py +++ b/src/hyperspell/types/integrations/web_crawler_index_response.py @@ -12,6 +12,7 @@ class WebCrawlerIndexResponse(BaseModel): source: Literal[ "collections", + "vault", "web_crawler", "notion", "slack", diff --git a/src/hyperspell/types/query_search_params.py b/src/hyperspell/types/query_search_params.py deleted file mode 100644 index d519c2df..00000000 --- a/src/hyperspell/types/query_search_params.py +++ /dev/null @@ -1,282 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from __future__ import annotations - -from typing import List, Union, Optional -from datetime import datetime -from typing_extensions import Literal, Required, Annotated, TypedDict - -from .._utils import PropertyInfo - -__all__ = [ - "QuerySearchParams", - "Filter", - "FilterGoogleCalendar", - "FilterGoogleMail", - "FilterNotion", - "FilterReddit", - "FilterSlack", - "FilterWebCrawler", - "Options", - "OptionsGoogleCalendar", - "OptionsGoogleMail", - "OptionsNotion", - "OptionsReddit", - "OptionsSlack", - "OptionsWebCrawler", -] - - -class QuerySearchParams(TypedDict, total=False): - query: Required[str] - """Query to run.""" - - answer: bool - """If true, the query will be answered along with matching source documents.""" - - filter: Optional[Filter] - """DEPRECATED: Use options instead. - - This field will be removed in a future version. - """ - - max_results: int - """Maximum number of results to return.""" - - options: Options - """Search options for the query.""" - - sources: List[ - Literal[ - "collections", - "web_crawler", - "notion", - "slack", - "google_calendar", - "reddit", - "box", - "google_drive", - "airtable", - "algolia", - "amplitude", - "asana", - "ashby", - "bamboohr", - "basecamp", - "bubbles", - "calendly", - "confluence", - "clickup", - "datadog", - "deel", - "discord", - "dropbox", - "exa", - "facebook", - "front", - "github", - "gitlab", - "google_docs", - "google_mail", - "google_sheet", - "hubspot", - "jira", - "linear", - "microsoft_teams", - "mixpanel", - "monday", - "outlook", - "perplexity", - "rippling", - "salesforce", - "segment", - "todoist", - "twitter", - "zoom", - ] - ] - """Only query documents from these sources.""" - - -class FilterGoogleCalendar(TypedDict, total=False): - calendar_id: Optional[str] - """The ID of the calendar to search. - - If not provided, it will use the ID of the default calendar. You can get the - list of calendars with the `/integrations/google_calendar/list` endpoint. - """ - - -class FilterGoogleMail(TypedDict, total=False): - label_ids: List[str] - """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']). - - Multiple labels are combined with OR logic - messages matching ANY specified - label will be returned. If empty, no label filtering is applied (searches all - accessible messages). - """ - - -class FilterNotion(TypedDict, total=False): - notion_page_ids: List[str] - """List of Notion page IDs to search. - - If not provided, all pages in the workspace will be searched. - """ - - -class FilterReddit(TypedDict, total=False): - period: Literal["hour", "day", "week", "month", "year", "all"] - """The time period to search. Defaults to 'month'.""" - - sort: Literal["relevance", "new", "hot", "top", "comments"] - """The sort order of the posts. Defaults to 'relevance'.""" - - subreddit: Optional[str] - """The subreddit to search. - - If not provided, the query will be searched for in all subreddits. - """ - - -class FilterSlack(TypedDict, total=False): - channels: List[str] - """List of Slack channels to search. - - If not provided, all channels in the workspace will be searched. - """ - - -class FilterWebCrawler(TypedDict, total=False): - max_depth: int - """Maximum depth to crawl from the starting URL""" - - url: Union[str, object] - """The URL to crawl""" - - -class Filter(TypedDict, total=False): - after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only query documents created on or after this date.""" - - before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only query documents created before this date.""" - - box: object - """Search options for Box""" - - collections: object - """Search options for Collection""" - - google_calendar: FilterGoogleCalendar - """Search options for Google Calendar""" - - google_drive: object - """Search options for Google Drive""" - - google_mail: FilterGoogleMail - """Search options for Gmail""" - - notion: FilterNotion - """Search options for Notion""" - - reddit: FilterReddit - """Search options for Reddit""" - - slack: FilterSlack - """Search options for Slack""" - - web_crawler: FilterWebCrawler - """Search options for Web Crawler""" - - -class OptionsGoogleCalendar(TypedDict, total=False): - calendar_id: Optional[str] - """The ID of the calendar to search. - - If not provided, it will use the ID of the default calendar. You can get the - list of calendars with the `/integrations/google_calendar/list` endpoint. - """ - - -class OptionsGoogleMail(TypedDict, total=False): - label_ids: List[str] - """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']). - - Multiple labels are combined with OR logic - messages matching ANY specified - label will be returned. If empty, no label filtering is applied (searches all - accessible messages). - """ - - -class OptionsNotion(TypedDict, total=False): - notion_page_ids: List[str] - """List of Notion page IDs to search. - - If not provided, all pages in the workspace will be searched. - """ - - -class OptionsReddit(TypedDict, total=False): - period: Literal["hour", "day", "week", "month", "year", "all"] - """The time period to search. Defaults to 'month'.""" - - sort: Literal["relevance", "new", "hot", "top", "comments"] - """The sort order of the posts. Defaults to 'relevance'.""" - - subreddit: Optional[str] - """The subreddit to search. - - If not provided, the query will be searched for in all subreddits. - """ - - -class OptionsSlack(TypedDict, total=False): - channels: List[str] - """List of Slack channels to search. - - If not provided, all channels in the workspace will be searched. - """ - - -class OptionsWebCrawler(TypedDict, total=False): - max_depth: int - """Maximum depth to crawl from the starting URL""" - - url: Union[str, object] - """The URL to crawl""" - - -class Options(TypedDict, total=False): - after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only query documents created on or after this date.""" - - before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] - """Only query documents created before this date.""" - - box: object - """Search options for Box""" - - collections: object - """Search options for Collection""" - - google_calendar: OptionsGoogleCalendar - """Search options for Google Calendar""" - - google_drive: object - """Search options for Google Drive""" - - google_mail: OptionsGoogleMail - """Search options for Gmail""" - - notion: OptionsNotion - """Search options for Notion""" - - reddit: OptionsReddit - """Search options for Reddit""" - - slack: OptionsSlack - """Search options for Slack""" - - web_crawler: OptionsWebCrawler - """Search options for Web Crawler""" diff --git a/src/hyperspell/types/query_search_response.py b/src/hyperspell/types/query_search_response.py deleted file mode 100644 index e02d76d4..00000000 --- a/src/hyperspell/types/query_search_response.py +++ /dev/null @@ -1,22 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional - -from .._models import BaseModel -from .document import Document - -__all__ = ["QuerySearchResponse"] - - -class QuerySearchResponse(BaseModel): - documents: List[Document] - - answer: Optional[str] = None - """The answer to the query, if the request was set to answer.""" - - errors: Optional[List[Dict[str, str]]] = None - """Errors that occurred during the query. - - These are meant to help the developer debug the query, and are not meant to be - shown to the user. - """ diff --git a/tests/api_resources/test_collections.py b/tests/api_resources/test_collections.py deleted file mode 100644 index 700a38ac..00000000 --- a/tests/api_resources/test_collections.py +++ /dev/null @@ -1,91 +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 hyperspell import Hyperspell, AsyncHyperspell -from tests.utils import assert_matches_type -from hyperspell.types import CollectionListResponse -from hyperspell.pagination import SyncCursorPage, AsyncCursorPage - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestCollections: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_list(self, client: Hyperspell) -> None: - collection = client.collections.list() - assert_matches_type(SyncCursorPage[CollectionListResponse], collection, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Hyperspell) -> None: - collection = client.collections.list( - cursor="cursor", - size=0, - ) - assert_matches_type(SyncCursorPage[CollectionListResponse], collection, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Hyperspell) -> None: - response = client.collections.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - collection = response.parse() - assert_matches_type(SyncCursorPage[CollectionListResponse], collection, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Hyperspell) -> None: - with client.collections.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - collection = response.parse() - assert_matches_type(SyncCursorPage[CollectionListResponse], collection, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncCollections: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @parametrize - async def test_method_list(self, async_client: AsyncHyperspell) -> None: - collection = await async_client.collections.list() - assert_matches_type(AsyncCursorPage[CollectionListResponse], collection, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None: - collection = await async_client.collections.list( - cursor="cursor", - size=0, - ) - assert_matches_type(AsyncCursorPage[CollectionListResponse], collection, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None: - response = await async_client.collections.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - collection = await response.parse() - assert_matches_type(AsyncCursorPage[CollectionListResponse], collection, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None: - async with async_client.collections.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - collection = await response.parse() - assert_matches_type(AsyncCursorPage[CollectionListResponse], collection, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_documents.py b/tests/api_resources/test_documents.py deleted file mode 100644 index caa44e7c..00000000 --- a/tests/api_resources/test_documents.py +++ /dev/null @@ -1,396 +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 hyperspell import Hyperspell, AsyncHyperspell -from tests.utils import assert_matches_type -from hyperspell.types import ( - Document, - DocumentStatus, - DocumentStatusResponse, -) -from hyperspell._utils import parse_datetime -from hyperspell.pagination import SyncCursorPage, AsyncCursorPage - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestDocuments: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_list(self, client: Hyperspell) -> None: - document = client.documents.list() - assert_matches_type(SyncCursorPage[Document], document, path=["response"]) - - @parametrize - def test_method_list_with_all_params(self, client: Hyperspell) -> None: - document = client.documents.list( - collection="collection", - cursor="cursor", - size=0, - source="collections", - ) - assert_matches_type(SyncCursorPage[Document], document, path=["response"]) - - @parametrize - def test_raw_response_list(self, client: Hyperspell) -> None: - response = client.documents.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = response.parse() - assert_matches_type(SyncCursorPage[Document], document, path=["response"]) - - @parametrize - def test_streaming_response_list(self, client: Hyperspell) -> None: - with client.documents.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = response.parse() - assert_matches_type(SyncCursorPage[Document], document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_add(self, client: Hyperspell) -> None: - document = client.documents.add( - text="text", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - def test_method_add_with_all_params(self, client: Hyperspell) -> None: - document = client.documents.add( - text="text", - collection="collection", - date=parse_datetime("2019-12-27T18:11:19.117Z"), - resource_id="resource_id", - title="title", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - def test_raw_response_add(self, client: Hyperspell) -> None: - response = client.documents.with_raw_response.add( - text="text", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - def test_streaming_response_add(self, client: Hyperspell) -> None: - with client.documents.with_streaming_response.add( - text="text", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_get(self, client: Hyperspell) -> None: - document = client.documents.get( - resource_id="resource_id", - source="collections", - ) - assert_matches_type(Document, document, path=["response"]) - - @parametrize - def test_raw_response_get(self, client: Hyperspell) -> None: - response = client.documents.with_raw_response.get( - resource_id="resource_id", - source="collections", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = response.parse() - assert_matches_type(Document, document, path=["response"]) - - @parametrize - def test_streaming_response_get(self, client: Hyperspell) -> None: - with client.documents.with_streaming_response.get( - resource_id="resource_id", - source="collections", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = response.parse() - assert_matches_type(Document, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_path_params_get(self, client: Hyperspell) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): - client.documents.with_raw_response.get( - resource_id="", - source="collections", - ) - - @parametrize - def test_method_status(self, client: Hyperspell) -> None: - document = client.documents.status() - assert_matches_type(DocumentStatusResponse, document, path=["response"]) - - @parametrize - def test_raw_response_status(self, client: Hyperspell) -> None: - response = client.documents.with_raw_response.status() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = response.parse() - assert_matches_type(DocumentStatusResponse, document, path=["response"]) - - @parametrize - def test_streaming_response_status(self, client: Hyperspell) -> None: - with client.documents.with_streaming_response.status() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = response.parse() - assert_matches_type(DocumentStatusResponse, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - def test_method_upload(self, client: Hyperspell) -> None: - document = client.documents.upload( - file=b"raw file contents", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - def test_method_upload_with_all_params(self, client: Hyperspell) -> None: - document = client.documents.upload( - file=b"raw file contents", - collection="collection", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - def test_raw_response_upload(self, client: Hyperspell) -> None: - response = client.documents.with_raw_response.upload( - file=b"raw file contents", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - def test_streaming_response_upload(self, client: Hyperspell) -> None: - with client.documents.with_streaming_response.upload( - file=b"raw file contents", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncDocuments: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @parametrize - async def test_method_list(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.list() - assert_matches_type(AsyncCursorPage[Document], document, path=["response"]) - - @parametrize - async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.list( - collection="collection", - cursor="cursor", - size=0, - source="collections", - ) - assert_matches_type(AsyncCursorPage[Document], document, path=["response"]) - - @parametrize - async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None: - response = await async_client.documents.with_raw_response.list() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = await response.parse() - assert_matches_type(AsyncCursorPage[Document], document, path=["response"]) - - @parametrize - async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None: - async with async_client.documents.with_streaming_response.list() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = await response.parse() - assert_matches_type(AsyncCursorPage[Document], document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_add(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.add( - text="text", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - async def test_method_add_with_all_params(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.add( - text="text", - collection="collection", - date=parse_datetime("2019-12-27T18:11:19.117Z"), - resource_id="resource_id", - title="title", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - async def test_raw_response_add(self, async_client: AsyncHyperspell) -> None: - response = await async_client.documents.with_raw_response.add( - text="text", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = await response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - async def test_streaming_response_add(self, async_client: AsyncHyperspell) -> None: - async with async_client.documents.with_streaming_response.add( - text="text", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = await response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_get(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.get( - resource_id="resource_id", - source="collections", - ) - assert_matches_type(Document, document, path=["response"]) - - @parametrize - async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None: - response = await async_client.documents.with_raw_response.get( - resource_id="resource_id", - source="collections", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = await response.parse() - assert_matches_type(Document, document, path=["response"]) - - @parametrize - async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> None: - async with async_client.documents.with_streaming_response.get( - resource_id="resource_id", - source="collections", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = await response.parse() - assert_matches_type(Document, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_path_params_get(self, async_client: AsyncHyperspell) -> None: - with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): - await async_client.documents.with_raw_response.get( - resource_id="", - source="collections", - ) - - @parametrize - async def test_method_status(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.status() - assert_matches_type(DocumentStatusResponse, document, path=["response"]) - - @parametrize - async def test_raw_response_status(self, async_client: AsyncHyperspell) -> None: - response = await async_client.documents.with_raw_response.status() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = await response.parse() - assert_matches_type(DocumentStatusResponse, document, path=["response"]) - - @parametrize - async def test_streaming_response_status(self, async_client: AsyncHyperspell) -> None: - async with async_client.documents.with_streaming_response.status() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = await response.parse() - assert_matches_type(DocumentStatusResponse, document, path=["response"]) - - assert cast(Any, response.is_closed) is True - - @parametrize - async def test_method_upload(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.upload( - file=b"raw file contents", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - async def test_method_upload_with_all_params(self, async_client: AsyncHyperspell) -> None: - document = await async_client.documents.upload( - file=b"raw file contents", - collection="collection", - ) - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - async def test_raw_response_upload(self, async_client: AsyncHyperspell) -> None: - response = await async_client.documents.with_raw_response.upload( - file=b"raw file contents", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - document = await response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - @parametrize - async def test_streaming_response_upload(self, async_client: AsyncHyperspell) -> None: - async with async_client.documents.with_streaming_response.upload( - file=b"raw file contents", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - document = await response.parse() - assert_matches_type(DocumentStatus, document, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_query.py b/tests/api_resources/test_query.py deleted file mode 100644 index d0ef69d2..00000000 --- a/tests/api_resources/test_query.py +++ /dev/null @@ -1,187 +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 hyperspell import Hyperspell, AsyncHyperspell -from tests.utils import assert_matches_type -from hyperspell.types import QuerySearchResponse -from hyperspell._utils import parse_datetime - -base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") - - -class TestQuery: - parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) - - @parametrize - def test_method_search(self, client: Hyperspell) -> None: - query = client.query.search( - query="query", - ) - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - @parametrize - def test_method_search_with_all_params(self, client: Hyperspell) -> None: - query = client.query.search( - query="query", - answer=True, - filter={ - "after": parse_datetime("2019-12-27T18:11:19.117Z"), - "before": parse_datetime("2019-12-27T18:11:19.117Z"), - "box": {}, - "collections": {}, - "google_calendar": {"calendar_id": "calendar_id"}, - "google_drive": {}, - "google_mail": {"label_ids": ["string"]}, - "notion": {"notion_page_ids": ["string"]}, - "reddit": { - "period": "hour", - "sort": "relevance", - "subreddit": "subreddit", - }, - "slack": {"channels": ["string"]}, - "web_crawler": { - "max_depth": 0, - "url": "string", - }, - }, - max_results=0, - options={ - "after": parse_datetime("2019-12-27T18:11:19.117Z"), - "before": parse_datetime("2019-12-27T18:11:19.117Z"), - "box": {}, - "collections": {}, - "google_calendar": {"calendar_id": "calendar_id"}, - "google_drive": {}, - "google_mail": {"label_ids": ["string"]}, - "notion": {"notion_page_ids": ["string"]}, - "reddit": { - "period": "hour", - "sort": "relevance", - "subreddit": "subreddit", - }, - "slack": {"channels": ["string"]}, - "web_crawler": { - "max_depth": 0, - "url": "string", - }, - }, - sources=["collections"], - ) - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - @parametrize - def test_raw_response_search(self, client: Hyperspell) -> None: - response = client.query.with_raw_response.search( - query="query", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query = response.parse() - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - @parametrize - def test_streaming_response_search(self, client: Hyperspell) -> None: - with client.query.with_streaming_response.search( - query="query", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - query = response.parse() - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - assert cast(Any, response.is_closed) is True - - -class TestAsyncQuery: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - - @parametrize - async def test_method_search(self, async_client: AsyncHyperspell) -> None: - query = await async_client.query.search( - query="query", - ) - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - @parametrize - async def test_method_search_with_all_params(self, async_client: AsyncHyperspell) -> None: - query = await async_client.query.search( - query="query", - answer=True, - filter={ - "after": parse_datetime("2019-12-27T18:11:19.117Z"), - "before": parse_datetime("2019-12-27T18:11:19.117Z"), - "box": {}, - "collections": {}, - "google_calendar": {"calendar_id": "calendar_id"}, - "google_drive": {}, - "google_mail": {"label_ids": ["string"]}, - "notion": {"notion_page_ids": ["string"]}, - "reddit": { - "period": "hour", - "sort": "relevance", - "subreddit": "subreddit", - }, - "slack": {"channels": ["string"]}, - "web_crawler": { - "max_depth": 0, - "url": "string", - }, - }, - max_results=0, - options={ - "after": parse_datetime("2019-12-27T18:11:19.117Z"), - "before": parse_datetime("2019-12-27T18:11:19.117Z"), - "box": {}, - "collections": {}, - "google_calendar": {"calendar_id": "calendar_id"}, - "google_drive": {}, - "google_mail": {"label_ids": ["string"]}, - "notion": {"notion_page_ids": ["string"]}, - "reddit": { - "period": "hour", - "sort": "relevance", - "subreddit": "subreddit", - }, - "slack": {"channels": ["string"]}, - "web_crawler": { - "max_depth": 0, - "url": "string", - }, - }, - sources=["collections"], - ) - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - @parametrize - async def test_raw_response_search(self, async_client: AsyncHyperspell) -> None: - response = await async_client.query.with_raw_response.search( - query="query", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - query = await response.parse() - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - @parametrize - async def test_streaming_response_search(self, async_client: AsyncHyperspell) -> None: - async with async_client.query.with_streaming_response.search( - query="query", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - query = await response.parse() - assert_matches_type(QuerySearchResponse, query, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/test_client.py b/tests/test_client.py index aeae2cbc..16015957 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -783,20 +783,20 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("hyperspell._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, client: Hyperspell) -> None: - respx_mock.post("/documents/add").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.documents.with_streaming_response.add(text="text").__enter__() + client.integrations.with_streaming_response.revoke("provider").__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("hyperspell._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, client: Hyperspell) -> None: - respx_mock.post("/documents/add").mock(return_value=httpx.Response(500)) + respx_mock.get("/integrations/provider/revoke").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.documents.with_streaming_response.add(text="text").__enter__() + client.integrations.with_streaming_response.revoke("provider").__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -823,9 +823,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/documents/add").mock(side_effect=retry_handler) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) - response = client.documents.with_raw_response.add(text="text") + response = client.integrations.with_raw_response.revoke("provider") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -847,10 +847,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/documents/add").mock(side_effect=retry_handler) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) - response = client.documents.with_raw_response.add( - text="text", extra_headers={"x-stainless-retry-count": Omit()} + response = client.integrations.with_raw_response.revoke( + "provider", extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -872,9 +872,11 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/documents/add").mock(side_effect=retry_handler) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) - response = client.documents.with_raw_response.add(text="text", extra_headers={"x-stainless-retry-count": "42"}) + response = client.integrations.with_raw_response.revoke( + "provider", extra_headers={"x-stainless-retry-count": "42"} + ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1662,10 +1664,10 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte async def test_retrying_timeout_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncHyperspell ) -> None: - respx_mock.post("/documents/add").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.documents.with_streaming_response.add(text="text").__aenter__() + await async_client.integrations.with_streaming_response.revoke("provider").__aenter__() assert _get_open_connections(self.client) == 0 @@ -1674,10 +1676,10 @@ async def test_retrying_timeout_errors_doesnt_leak( async def test_retrying_status_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncHyperspell ) -> None: - respx_mock.post("/documents/add").mock(return_value=httpx.Response(500)) + respx_mock.get("/integrations/provider/revoke").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.documents.with_streaming_response.add(text="text").__aenter__() + await async_client.integrations.with_streaming_response.revoke("provider").__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1705,9 +1707,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/documents/add").mock(side_effect=retry_handler) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) - response = await client.documents.with_raw_response.add(text="text") + response = await client.integrations.with_raw_response.revoke("provider") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1730,10 +1732,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/documents/add").mock(side_effect=retry_handler) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) - response = await client.documents.with_raw_response.add( - text="text", extra_headers={"x-stainless-retry-count": Omit()} + response = await client.integrations.with_raw_response.revoke( + "provider", extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1756,10 +1758,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.post("/documents/add").mock(side_effect=retry_handler) + respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) - response = await client.documents.with_raw_response.add( - text="text", extra_headers={"x-stainless-retry-count": "42"} + response = await client.integrations.with_raw_response.revoke( + "provider", extra_headers={"x-stainless-retry-count": "42"} ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From 82ee242a6eccb0d069cfbbf591facccf3d2f2fe8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:31:31 +0000 Subject: [PATCH 28/33] codegen metadata --- .stats.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index bff33e9b..eeb39148 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 5 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-e69401a4b445430dd5b4272d4d7084a88fe9082d96931fdd91f6a12b1c851fa3.yml -openapi_spec_hash: ef23d82a9c899f2dfdcdd537890a22e6 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-4bfa5ab6a0021f526d6f6f622f1872f2ec3467b6d1bd51acfc17bfc6f399f915.yml +openapi_spec_hash: 5b51df5010bab70a1f3f884552ce25c3 config_hash: 69b1946a9113d987e8fccb53eefba74a From bf294db204ddb25338ab2370d78d6c378c78686a Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:17:54 +0000 Subject: [PATCH 29/33] feat(api): update via SDK Studio --- .stats.yml | 4 +- README.md | 33 + api.md | 25 +- src/hyperspell/_client.py | 18 +- src/hyperspell/_files.py | 2 +- src/hyperspell/resources/__init__.py | 28 + src/hyperspell/resources/memories.py | 1031 +++++++++++++++++ src/hyperspell/resources/vaults.py | 192 +++ src/hyperspell/types/__init__.py | 10 + src/hyperspell/types/document.py | 91 ++ src/hyperspell/types/document_status.py | 65 ++ src/hyperspell/types/memory_add_params.py | 38 + src/hyperspell/types/memory_list_params.py | 69 ++ src/hyperspell/types/memory_search_params.py | 289 +++++ .../types/memory_search_response.py | 22 + .../types/memory_status_response.py | 13 + src/hyperspell/types/memory_upload_params.py | 18 + src/hyperspell/types/vault_list_params.py | 14 + src/hyperspell/types/vault_list_response.py | 13 + tests/api_resources/test_memories.py | 563 +++++++++ tests/api_resources/test_vaults.py | 91 ++ 21 files changed, 2623 insertions(+), 6 deletions(-) create mode 100644 src/hyperspell/resources/memories.py create mode 100644 src/hyperspell/resources/vaults.py create mode 100644 src/hyperspell/types/document.py create mode 100644 src/hyperspell/types/document_status.py create mode 100644 src/hyperspell/types/memory_add_params.py create mode 100644 src/hyperspell/types/memory_list_params.py create mode 100644 src/hyperspell/types/memory_search_params.py create mode 100644 src/hyperspell/types/memory_search_response.py create mode 100644 src/hyperspell/types/memory_status_response.py create mode 100644 src/hyperspell/types/memory_upload_params.py create mode 100644 src/hyperspell/types/vault_list_params.py create mode 100644 src/hyperspell/types/vault_list_response.py create mode 100644 tests/api_resources/test_memories.py create mode 100644 tests/api_resources/test_vaults.py diff --git a/.stats.yml b/.stats.yml index eeb39148..798d9204 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 5 +configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-4bfa5ab6a0021f526d6f6f622f1872f2ec3467b6d1bd51acfc17bfc6f399f915.yml openapi_spec_hash: 5b51df5010bab70a1f3f884552ce25c3 -config_hash: 69b1946a9113d987e8fccb53eefba74a +config_hash: 07a0a7ad518dab7df0aed6882f9d760b diff --git a/README.md b/README.md index 83dfd642..d6e40d8a 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,39 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Nested params + +Nested parameters are dictionaries, typed using `TypedDict`, for example: + +```python +from hyperspell import Hyperspell + +client = Hyperspell() + +response = client.memories.search( + query="query", + filter={}, +) +print(response.filter) +``` + +## File uploads + +Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`. + +```python +from pathlib import Path +from hyperspell import Hyperspell + +client = Hyperspell() + +client.memories.upload( + file=Path("/path/to/file"), +) +``` + +The async client uses the exact same interface. If you pass a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance, the file contents will be read asynchronously automatically. + ## Handling errors When the library is unable to connect to the API (for example, due to network connection problems or a timeout), a subclass of `hyperspell.APIConnectionError` is raised. diff --git a/api.md b/api.md index 1fdf4281..d6e8f501 100644 --- a/api.md +++ b/api.md @@ -34,14 +34,35 @@ Methods: - client.integrations.web_crawler.index(\*\*params) -> WebCrawlerIndexResponse -# Documents +# Memories Types: ```python -from hyperspell.types import Document, DocumentStatus +from hyperspell.types import Document, DocumentStatus, MemorySearchResponse, MemoryStatusResponse ``` +Methods: + +- client.memories.list(\*\*params) -> SyncCursorPage[Document] +- client.memories.add(\*\*params) -> DocumentStatus +- client.memories.get(resource_id, \*, source) -> Document +- client.memories.search(\*\*params) -> MemorySearchResponse +- client.memories.status() -> MemoryStatusResponse +- client.memories.upload(\*\*params) -> DocumentStatus + +# Vaults + +Types: + +```python +from hyperspell.types import VaultListResponse +``` + +Methods: + +- client.vaults.list(\*\*params) -> SyncCursorPage[VaultListResponse] + # Auth Types: diff --git a/src/hyperspell/_client.py b/src/hyperspell/_client.py index e5450ae1..4c5bd535 100644 --- a/src/hyperspell/_client.py +++ b/src/hyperspell/_client.py @@ -21,7 +21,7 @@ ) from ._utils import is_given, get_async_library from ._version import __version__ -from .resources import auth +from .resources import auth, vaults, memories from ._streaming import Stream as Stream, AsyncStream as AsyncStream from ._exceptions import APIStatusError, HyperspellError from ._base_client import ( @@ -45,6 +45,8 @@ class Hyperspell(SyncAPIClient): integrations: integrations.IntegrationsResource + memories: memories.MemoriesResource + vaults: vaults.VaultsResource auth: auth.AuthResource with_raw_response: HyperspellWithRawResponse with_streaming_response: HyperspellWithStreamedResponse @@ -108,6 +110,8 @@ def __init__( ) self.integrations = integrations.IntegrationsResource(self) + self.memories = memories.MemoriesResource(self) + self.vaults = vaults.VaultsResource(self) self.auth = auth.AuthResource(self) self.with_raw_response = HyperspellWithRawResponse(self) self.with_streaming_response = HyperspellWithStreamedResponse(self) @@ -232,6 +236,8 @@ def _make_status_error( class AsyncHyperspell(AsyncAPIClient): integrations: integrations.AsyncIntegrationsResource + memories: memories.AsyncMemoriesResource + vaults: vaults.AsyncVaultsResource auth: auth.AsyncAuthResource with_raw_response: AsyncHyperspellWithRawResponse with_streaming_response: AsyncHyperspellWithStreamedResponse @@ -295,6 +301,8 @@ def __init__( ) self.integrations = integrations.AsyncIntegrationsResource(self) + self.memories = memories.AsyncMemoriesResource(self) + self.vaults = vaults.AsyncVaultsResource(self) self.auth = auth.AsyncAuthResource(self) self.with_raw_response = AsyncHyperspellWithRawResponse(self) self.with_streaming_response = AsyncHyperspellWithStreamedResponse(self) @@ -420,24 +428,32 @@ def _make_status_error( class HyperspellWithRawResponse: def __init__(self, client: Hyperspell) -> None: self.integrations = integrations.IntegrationsResourceWithRawResponse(client.integrations) + self.memories = memories.MemoriesResourceWithRawResponse(client.memories) + self.vaults = vaults.VaultsResourceWithRawResponse(client.vaults) self.auth = auth.AuthResourceWithRawResponse(client.auth) class AsyncHyperspellWithRawResponse: def __init__(self, client: AsyncHyperspell) -> None: self.integrations = integrations.AsyncIntegrationsResourceWithRawResponse(client.integrations) + self.memories = memories.AsyncMemoriesResourceWithRawResponse(client.memories) + self.vaults = vaults.AsyncVaultsResourceWithRawResponse(client.vaults) self.auth = auth.AsyncAuthResourceWithRawResponse(client.auth) class HyperspellWithStreamedResponse: def __init__(self, client: Hyperspell) -> None: self.integrations = integrations.IntegrationsResourceWithStreamingResponse(client.integrations) + self.memories = memories.MemoriesResourceWithStreamingResponse(client.memories) + self.vaults = vaults.VaultsResourceWithStreamingResponse(client.vaults) self.auth = auth.AuthResourceWithStreamingResponse(client.auth) class AsyncHyperspellWithStreamedResponse: def __init__(self, client: AsyncHyperspell) -> None: self.integrations = integrations.AsyncIntegrationsResourceWithStreamingResponse(client.integrations) + self.memories = memories.AsyncMemoriesResourceWithStreamingResponse(client.memories) + self.vaults = vaults.AsyncVaultsResourceWithStreamingResponse(client.vaults) self.auth = auth.AsyncAuthResourceWithStreamingResponse(client.auth) diff --git a/src/hyperspell/_files.py b/src/hyperspell/_files.py index 715cc207..1d0a3358 100644 --- a/src/hyperspell/_files.py +++ b/src/hyperspell/_files.py @@ -34,7 +34,7 @@ def assert_is_file_content(obj: object, *, key: str | None = None) -> None: if not is_file_content(obj): prefix = f"Expected entry at `{key}`" if key is not None else f"Expected file input `{obj!r}`" raise RuntimeError( - f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead." + f"{prefix} to be bytes, an io.IOBase instance, PathLike or a tuple but received {type(obj)} instead. See https://github.com/hyperspell/python-sdk/tree/main#file-uploads" ) from None diff --git a/src/hyperspell/resources/__init__.py b/src/hyperspell/resources/__init__.py index de0f600d..71ddb874 100644 --- a/src/hyperspell/resources/__init__.py +++ b/src/hyperspell/resources/__init__.py @@ -8,6 +8,22 @@ AuthResourceWithStreamingResponse, AsyncAuthResourceWithStreamingResponse, ) +from .vaults import ( + VaultsResource, + AsyncVaultsResource, + VaultsResourceWithRawResponse, + AsyncVaultsResourceWithRawResponse, + VaultsResourceWithStreamingResponse, + AsyncVaultsResourceWithStreamingResponse, +) +from .memories import ( + MemoriesResource, + AsyncMemoriesResource, + MemoriesResourceWithRawResponse, + AsyncMemoriesResourceWithRawResponse, + MemoriesResourceWithStreamingResponse, + AsyncMemoriesResourceWithStreamingResponse, +) from .integrations import ( IntegrationsResource, AsyncIntegrationsResource, @@ -24,6 +40,18 @@ "AsyncIntegrationsResourceWithRawResponse", "IntegrationsResourceWithStreamingResponse", "AsyncIntegrationsResourceWithStreamingResponse", + "MemoriesResource", + "AsyncMemoriesResource", + "MemoriesResourceWithRawResponse", + "AsyncMemoriesResourceWithRawResponse", + "MemoriesResourceWithStreamingResponse", + "AsyncMemoriesResourceWithStreamingResponse", + "VaultsResource", + "AsyncVaultsResource", + "VaultsResourceWithRawResponse", + "AsyncVaultsResourceWithRawResponse", + "VaultsResourceWithStreamingResponse", + "AsyncVaultsResourceWithStreamingResponse", "AuthResource", "AsyncAuthResource", "AuthResourceWithRawResponse", diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py new file mode 100644 index 00000000..94635353 --- /dev/null +++ b/src/hyperspell/resources/memories.py @@ -0,0 +1,1031 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Mapping, Optional, cast +from datetime import datetime +from typing_extensions import Literal + +import httpx + +from ..types import memory_add_params, memory_list_params, memory_search_params, memory_upload_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes +from .._utils import extract_files, maybe_transform, deepcopy_minimal, async_maybe_transform +from .._compat import cached_property +from .._resource import SyncAPIResource, AsyncAPIResource +from .._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.document import Document +from ..types.document_status import DocumentStatus +from ..types.memory_search_response import MemorySearchResponse +from ..types.memory_status_response import MemoryStatusResponse + +__all__ = ["MemoriesResource", "AsyncMemoriesResource"] + + +class MemoriesResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> MemoriesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers + """ + return MemoriesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response + """ + return MemoriesResourceWithStreamingResponse(self) + + def list( + self, + *, + collection: Optional[str] | NotGiven = NOT_GIVEN, + cursor: Optional[str] | NotGiven = NOT_GIVEN, + size: int | NotGiven = NOT_GIVEN, + source: Optional[ + Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + ] + | 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, + ) -> SyncCursorPage[Document]: + """This endpoint allows you to paginate through all documents in the index. + + You can + filter the documents by title, date, metadata, etc. + + Args: + collection: Filter documents by collection. + + source: Filter documents by source. + + 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_api_list( + "/memories/list", + page=SyncCursorPage[Document], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "collection": collection, + "cursor": cursor, + "size": size, + "source": source, + }, + memory_list_params.MemoryListParams, + ), + ), + model=Document, + ) + + def add( + self, + *, + text: str, + collection: Optional[str] | NotGiven = NOT_GIVEN, + date: Union[str, datetime] | NotGiven = NOT_GIVEN, + resource_id: str | NotGiven = NOT_GIVEN, + title: Optional[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, + ) -> DocumentStatus: + """Adds an arbitrary document to the index. + + This can be any text, email, call + transcript, etc. The document will be processed and made available for querying + once the processing is complete. + + Args: + text: Full text of the document. + + collection: The collection to add the document to for easier retrieval. + + date: Date of the document. Depending on the document, this could be the creation date + or date the document was last updated (eg. for a chat transcript, this would be + the date of the last message). This helps the ranking algorithm and allows you + to filter by date range. + + resource_id: The resource ID to add the document to. If not provided, a new resource ID will + be generated. If provided, the document will be updated if it already exists. + + title: Title of the document. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/memories/add", + body=maybe_transform( + { + "text": text, + "collection": collection, + "date": date, + "resource_id": resource_id, + "title": title, + }, + memory_add_params.MemoryAddParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DocumentStatus, + ) + + def get( + self, + resource_id: str, + *, + source: Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Document: + """ + Retrieves a document by provider and resource_id. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not source: + raise ValueError(f"Expected a non-empty value for `source` but received {source!r}") + if not resource_id: + raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}") + return self._get( + f"/memories/get/{source}/{resource_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Document, + ) + + def search( + self, + *, + query: str, + answer: bool | NotGiven = NOT_GIVEN, + filter: Optional[memory_search_params.Filter] | NotGiven = NOT_GIVEN, + max_results: int | NotGiven = NOT_GIVEN, + options: memory_search_params.Options | NotGiven = NOT_GIVEN, + sources: List[ + Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + ] + | 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, + ) -> MemorySearchResponse: + """ + Retrieves documents matching the query. + + Args: + query: Query to run. + + answer: If true, the query will be answered along with matching source documents. + + filter: DEPRECATED: Use options instead. This field will be removed in a future version. + + max_results: Maximum number of results to return. + + options: Search options for the query. + + sources: Only query documents from these sources. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return self._post( + "/memories/query", + body=maybe_transform( + { + "query": query, + "answer": answer, + "filter": filter, + "max_results": max_results, + "options": options, + "sources": sources, + }, + memory_search_params.MemorySearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemorySearchResponse, + ) + + def status( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> MemoryStatusResponse: + """ + This endpoint shows the indexing progress of documents, both by provider and + total. + """ + return self._get( + "/memories/status", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryStatusResponse, + ) + + def upload( + self, + *, + file: FileTypes, + collection: Optional[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, + ) -> DocumentStatus: + """This endpoint will upload a file to the index and return a document ID. + + The file + will be processed in the background and the document will be available for + querying once the processing is complete. You can use the `document_id` to query + the document later, and check the status of the document. + + Args: + file: The file to ingest. + + collection: The collection to add the document to. + + 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, + "collection": collection, + } + ) + 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( + "/memories/upload", + body=maybe_transform(body, memory_upload_params.MemoryUploadParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DocumentStatus, + ) + + +class AsyncMemoriesResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncMemoriesResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/hyperspell/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncMemoriesResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response + """ + return AsyncMemoriesResourceWithStreamingResponse(self) + + def list( + self, + *, + collection: Optional[str] | NotGiven = NOT_GIVEN, + cursor: Optional[str] | NotGiven = NOT_GIVEN, + size: int | NotGiven = NOT_GIVEN, + source: Optional[ + Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + ] + | 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, + ) -> AsyncPaginator[Document, AsyncCursorPage[Document]]: + """This endpoint allows you to paginate through all documents in the index. + + You can + filter the documents by title, date, metadata, etc. + + Args: + collection: Filter documents by collection. + + source: Filter documents by source. + + 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_api_list( + "/memories/list", + page=AsyncCursorPage[Document], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "collection": collection, + "cursor": cursor, + "size": size, + "source": source, + }, + memory_list_params.MemoryListParams, + ), + ), + model=Document, + ) + + async def add( + self, + *, + text: str, + collection: Optional[str] | NotGiven = NOT_GIVEN, + date: Union[str, datetime] | NotGiven = NOT_GIVEN, + resource_id: str | NotGiven = NOT_GIVEN, + title: Optional[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, + ) -> DocumentStatus: + """Adds an arbitrary document to the index. + + This can be any text, email, call + transcript, etc. The document will be processed and made available for querying + once the processing is complete. + + Args: + text: Full text of the document. + + collection: The collection to add the document to for easier retrieval. + + date: Date of the document. Depending on the document, this could be the creation date + or date the document was last updated (eg. for a chat transcript, this would be + the date of the last message). This helps the ranking algorithm and allows you + to filter by date range. + + resource_id: The resource ID to add the document to. If not provided, a new resource ID will + be generated. If provided, the document will be updated if it already exists. + + title: Title of the document. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/memories/add", + body=await async_maybe_transform( + { + "text": text, + "collection": collection, + "date": date, + "resource_id": resource_id, + "title": title, + }, + memory_add_params.MemoryAddParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DocumentStatus, + ) + + async def get( + self, + resource_id: str, + *, + source: Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ], + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> Document: + """ + Retrieves a document by provider and resource_id. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not source: + raise ValueError(f"Expected a non-empty value for `source` but received {source!r}") + if not resource_id: + raise ValueError(f"Expected a non-empty value for `resource_id` but received {resource_id!r}") + return await self._get( + f"/memories/get/{source}/{resource_id}", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=Document, + ) + + async def search( + self, + *, + query: str, + answer: bool | NotGiven = NOT_GIVEN, + filter: Optional[memory_search_params.Filter] | NotGiven = NOT_GIVEN, + max_results: int | NotGiven = NOT_GIVEN, + options: memory_search_params.Options | NotGiven = NOT_GIVEN, + sources: List[ + Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + ] + | 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, + ) -> MemorySearchResponse: + """ + Retrieves documents matching the query. + + Args: + query: Query to run. + + answer: If true, the query will be answered along with matching source documents. + + filter: DEPRECATED: Use options instead. This field will be removed in a future version. + + max_results: Maximum number of results to return. + + options: Search options for the query. + + sources: Only query documents from these sources. + + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + return await self._post( + "/memories/query", + body=await async_maybe_transform( + { + "query": query, + "answer": answer, + "filter": filter, + "max_results": max_results, + "options": options, + "sources": sources, + }, + memory_search_params.MemorySearchParams, + ), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemorySearchResponse, + ) + + async def status( + self, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, + ) -> MemoryStatusResponse: + """ + This endpoint shows the indexing progress of documents, both by provider and + total. + """ + return await self._get( + "/memories/status", + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=MemoryStatusResponse, + ) + + async def upload( + self, + *, + file: FileTypes, + collection: Optional[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, + ) -> DocumentStatus: + """This endpoint will upload a file to the index and return a document ID. + + The file + will be processed in the background and the document will be available for + querying once the processing is complete. You can use the `document_id` to query + the document later, and check the status of the document. + + Args: + file: The file to ingest. + + collection: The collection to add the document to. + + 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, + "collection": collection, + } + ) + 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( + "/memories/upload", + body=await async_maybe_transform(body, memory_upload_params.MemoryUploadParams), + files=files, + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=DocumentStatus, + ) + + +class MemoriesResourceWithRawResponse: + def __init__(self, memories: MemoriesResource) -> None: + self._memories = memories + + self.list = to_raw_response_wrapper( + memories.list, + ) + self.add = to_raw_response_wrapper( + memories.add, + ) + self.get = to_raw_response_wrapper( + memories.get, + ) + self.search = to_raw_response_wrapper( + memories.search, + ) + self.status = to_raw_response_wrapper( + memories.status, + ) + self.upload = to_raw_response_wrapper( + memories.upload, + ) + + +class AsyncMemoriesResourceWithRawResponse: + def __init__(self, memories: AsyncMemoriesResource) -> None: + self._memories = memories + + self.list = async_to_raw_response_wrapper( + memories.list, + ) + self.add = async_to_raw_response_wrapper( + memories.add, + ) + self.get = async_to_raw_response_wrapper( + memories.get, + ) + self.search = async_to_raw_response_wrapper( + memories.search, + ) + self.status = async_to_raw_response_wrapper( + memories.status, + ) + self.upload = async_to_raw_response_wrapper( + memories.upload, + ) + + +class MemoriesResourceWithStreamingResponse: + def __init__(self, memories: MemoriesResource) -> None: + self._memories = memories + + self.list = to_streamed_response_wrapper( + memories.list, + ) + self.add = to_streamed_response_wrapper( + memories.add, + ) + self.get = to_streamed_response_wrapper( + memories.get, + ) + self.search = to_streamed_response_wrapper( + memories.search, + ) + self.status = to_streamed_response_wrapper( + memories.status, + ) + self.upload = to_streamed_response_wrapper( + memories.upload, + ) + + +class AsyncMemoriesResourceWithStreamingResponse: + def __init__(self, memories: AsyncMemoriesResource) -> None: + self._memories = memories + + self.list = async_to_streamed_response_wrapper( + memories.list, + ) + self.add = async_to_streamed_response_wrapper( + memories.add, + ) + self.get = async_to_streamed_response_wrapper( + memories.get, + ) + self.search = async_to_streamed_response_wrapper( + memories.search, + ) + self.status = async_to_streamed_response_wrapper( + memories.status, + ) + self.upload = async_to_streamed_response_wrapper( + memories.upload, + ) diff --git a/src/hyperspell/resources/vaults.py b/src/hyperspell/resources/vaults.py new file mode 100644 index 00000000..babb8a04 --- /dev/null +++ b/src/hyperspell/resources/vaults.py @@ -0,0 +1,192 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional + +import httpx + +from ..types import vault_list_params +from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven +from .._utils import 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 ..pagination import SyncCursorPage, AsyncCursorPage +from .._base_client import AsyncPaginator, make_request_options +from ..types.vault_list_response import VaultListResponse + +__all__ = ["VaultsResource", "AsyncVaultsResource"] + + +class VaultsResource(SyncAPIResource): + @cached_property + def with_raw_response(self) -> VaultsResourceWithRawResponse: + """ + 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers + """ + return VaultsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> VaultsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response + """ + return VaultsResourceWithStreamingResponse(self) + + def list( + self, + *, + cursor: Optional[str] | NotGiven = NOT_GIVEN, + size: int | 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, + ) -> SyncCursorPage[VaultListResponse]: + """ + This endpoint lists all collections, and how many documents are in each + collection. All documents that do not have a collection assigned are in the + `null` collection. + + 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_api_list( + "/vault/list", + page=SyncCursorPage[VaultListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "size": size, + }, + vault_list_params.VaultListParams, + ), + ), + model=VaultListResponse, + ) + + +class AsyncVaultsResource(AsyncAPIResource): + @cached_property + def with_raw_response(self) -> AsyncVaultsResourceWithRawResponse: + """ + 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/hyperspell/python-sdk#accessing-raw-response-data-eg-headers + """ + return AsyncVaultsResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncVaultsResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/hyperspell/python-sdk#with_streaming_response + """ + return AsyncVaultsResourceWithStreamingResponse(self) + + def list( + self, + *, + cursor: Optional[str] | NotGiven = NOT_GIVEN, + size: int | 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, + ) -> AsyncPaginator[VaultListResponse, AsyncCursorPage[VaultListResponse]]: + """ + This endpoint lists all collections, and how many documents are in each + collection. All documents that do not have a collection assigned are in the + `null` collection. + + 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_api_list( + "/vault/list", + page=AsyncCursorPage[VaultListResponse], + options=make_request_options( + extra_headers=extra_headers, + extra_query=extra_query, + extra_body=extra_body, + timeout=timeout, + query=maybe_transform( + { + "cursor": cursor, + "size": size, + }, + vault_list_params.VaultListParams, + ), + ), + model=VaultListResponse, + ) + + +class VaultsResourceWithRawResponse: + def __init__(self, vaults: VaultsResource) -> None: + self._vaults = vaults + + self.list = to_raw_response_wrapper( + vaults.list, + ) + + +class AsyncVaultsResourceWithRawResponse: + def __init__(self, vaults: AsyncVaultsResource) -> None: + self._vaults = vaults + + self.list = async_to_raw_response_wrapper( + vaults.list, + ) + + +class VaultsResourceWithStreamingResponse: + def __init__(self, vaults: VaultsResource) -> None: + self._vaults = vaults + + self.list = to_streamed_response_wrapper( + vaults.list, + ) + + +class AsyncVaultsResourceWithStreamingResponse: + def __init__(self, vaults: AsyncVaultsResource) -> None: + self._vaults = vaults + + self.list = async_to_streamed_response_wrapper( + vaults.list, + ) diff --git a/src/hyperspell/types/__init__.py b/src/hyperspell/types/__init__.py index 06b0608b..99655381 100644 --- a/src/hyperspell/types/__init__.py +++ b/src/hyperspell/types/__init__.py @@ -3,6 +3,16 @@ from __future__ import annotations from .token import Token as Token +from .document import Document as Document +from .document_status import DocumentStatus as DocumentStatus from .auth_me_response import AuthMeResponse as AuthMeResponse +from .memory_add_params import MemoryAddParams as MemoryAddParams +from .vault_list_params import VaultListParams as VaultListParams +from .memory_list_params import MemoryListParams as MemoryListParams +from .vault_list_response import VaultListResponse as VaultListResponse +from .memory_search_params import MemorySearchParams as MemorySearchParams +from .memory_upload_params import MemoryUploadParams as MemoryUploadParams from .auth_user_token_params import AuthUserTokenParams as AuthUserTokenParams +from .memory_search_response import MemorySearchResponse as MemorySearchResponse +from .memory_status_response import MemoryStatusResponse as MemoryStatusResponse from .integration_revoke_response import IntegrationRevokeResponse as IntegrationRevokeResponse diff --git a/src/hyperspell/types/document.py b/src/hyperspell/types/document.py new file mode 100644 index 00000000..6d22a128 --- /dev/null +++ b/src/hyperspell/types/document.py @@ -0,0 +1,91 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import TYPE_CHECKING, List, Optional +from datetime import datetime +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["Document", "Metadata", "MetadataEvent"] + + +class MetadataEvent(BaseModel): + message: str + + type: Literal["error", "warning", "info", "success"] + + time: Optional[datetime] = None + + +class Metadata(BaseModel): + events: Optional[List[MetadataEvent]] = None + + indexed_at: Optional[datetime] = None + + last_modified: Optional[datetime] = None + + status: Optional[Literal["pending", "processing", "completed", "failed"]] = None + + if TYPE_CHECKING: + # Stub to indicate that arbitrary properties are accepted. + # To access properties that are not valid identifiers you can use `getattr`, e.g. + # `getattr(obj, '$type')` + def __getattr__(self, attr: str) -> object: ... + + +class Document(BaseModel): + resource_id: str + + source: Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + + metadata: Optional[Metadata] = None + + score: Optional[float] = None + """The relevance of the resource to the query""" diff --git a/src/hyperspell/types/document_status.py b/src/hyperspell/types/document_status.py new file mode 100644 index 00000000..0fb15e5a --- /dev/null +++ b/src/hyperspell/types/document_status.py @@ -0,0 +1,65 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing_extensions import Literal + +from .._models import BaseModel + +__all__ = ["DocumentStatus"] + + +class DocumentStatus(BaseModel): + id: int + """Deprecated: refer to documents by source and resource_id instead""" + + resource_id: str + + source: Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + + status: Literal["pending", "processing", "completed", "failed"] diff --git a/src/hyperspell/types/memory_add_params.py b/src/hyperspell/types/memory_add_params.py new file mode 100644 index 00000000..5c94f1db --- /dev/null +++ b/src/hyperspell/types/memory_add_params.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Union, Optional +from datetime import datetime +from typing_extensions import Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = ["MemoryAddParams"] + + +class MemoryAddParams(TypedDict, total=False): + text: Required[str] + """Full text of the document.""" + + collection: Optional[str] + """The collection to add the document to for easier retrieval.""" + + date: Annotated[Union[str, datetime], PropertyInfo(format="iso8601")] + """Date of the document. + + Depending on the document, this could be the creation date or date the document + was last updated (eg. for a chat transcript, this would be the date of the last + message). This helps the ranking algorithm and allows you to filter by date + range. + """ + + resource_id: str + """The resource ID to add the document to. + + If not provided, a new resource ID will be generated. If provided, the document + will be updated if it already exists. + """ + + title: Optional[str] + """Title of the document.""" diff --git a/src/hyperspell/types/memory_list_params.py b/src/hyperspell/types/memory_list_params.py new file mode 100644 index 00000000..c2679fb0 --- /dev/null +++ b/src/hyperspell/types/memory_list_params.py @@ -0,0 +1,69 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Literal, TypedDict + +__all__ = ["MemoryListParams"] + + +class MemoryListParams(TypedDict, total=False): + collection: Optional[str] + """Filter documents by collection.""" + + cursor: Optional[str] + + size: int + + source: Optional[ + Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + ] + """Filter documents by source.""" diff --git a/src/hyperspell/types/memory_search_params.py b/src/hyperspell/types/memory_search_params.py new file mode 100644 index 00000000..9f97bc11 --- /dev/null +++ b/src/hyperspell/types/memory_search_params.py @@ -0,0 +1,289 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import List, Union, Optional +from datetime import datetime +from typing_extensions import Literal, Required, Annotated, TypedDict + +from .._utils import PropertyInfo + +__all__ = [ + "MemorySearchParams", + "Filter", + "FilterGoogleCalendar", + "FilterGoogleMail", + "FilterNotion", + "FilterReddit", + "FilterSlack", + "FilterWebCrawler", + "Options", + "OptionsGoogleCalendar", + "OptionsGoogleMail", + "OptionsNotion", + "OptionsReddit", + "OptionsSlack", + "OptionsWebCrawler", +] + + +class MemorySearchParams(TypedDict, total=False): + query: Required[str] + """Query to run.""" + + answer: bool + """If true, the query will be answered along with matching source documents.""" + + filter: Optional[Filter] + """DEPRECATED: Use options instead. + + This field will be removed in a future version. + """ + + max_results: int + """Maximum number of results to return.""" + + options: Options + """Search options for the query.""" + + sources: List[ + Literal[ + "collections", + "vault", + "web_crawler", + "notion", + "slack", + "google_calendar", + "reddit", + "box", + "google_drive", + "airtable", + "algolia", + "amplitude", + "asana", + "ashby", + "bamboohr", + "basecamp", + "bubbles", + "calendly", + "confluence", + "clickup", + "datadog", + "deel", + "discord", + "dropbox", + "exa", + "facebook", + "front", + "github", + "gitlab", + "google_docs", + "google_mail", + "google_sheet", + "hubspot", + "jira", + "linear", + "microsoft_teams", + "mixpanel", + "monday", + "outlook", + "perplexity", + "rippling", + "salesforce", + "segment", + "todoist", + "twitter", + "zoom", + ] + ] + """Only query documents from these sources.""" + + +class FilterGoogleCalendar(TypedDict, total=False): + calendar_id: Optional[str] + """The ID of the calendar to search. + + If not provided, it will use the ID of the default calendar. You can get the + list of calendars with the `/integrations/google_calendar/list` endpoint. + """ + + +class FilterGoogleMail(TypedDict, total=False): + label_ids: List[str] + """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']). + + Multiple labels are combined with OR logic - messages matching ANY specified + label will be returned. If empty, no label filtering is applied (searches all + accessible messages). + """ + + +class FilterNotion(TypedDict, total=False): + notion_page_ids: List[str] + """List of Notion page IDs to search. + + If not provided, all pages in the workspace will be searched. + """ + + +class FilterReddit(TypedDict, total=False): + period: Literal["hour", "day", "week", "month", "year", "all"] + """The time period to search. Defaults to 'month'.""" + + sort: Literal["relevance", "new", "hot", "top", "comments"] + """The sort order of the posts. Defaults to 'relevance'.""" + + subreddit: Optional[str] + """The subreddit to search. + + If not provided, the query will be searched for in all subreddits. + """ + + +class FilterSlack(TypedDict, total=False): + channels: List[str] + """List of Slack channels to search. + + If not provided, all channels in the workspace will be searched. + """ + + +class FilterWebCrawler(TypedDict, total=False): + max_depth: int + """Maximum depth to crawl from the starting URL""" + + url: Union[str, object] + """The URL to crawl""" + + +class Filter(TypedDict, total=False): + after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only query documents created on or after this date.""" + + answer_model: Literal["llama-3.1", "gemma2", "qwen-qwq", "mistral-saba", "llama-4-scout", "deepseek-r1"] + """Model to use for answer generation when answer=True""" + + before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only query documents created before this date.""" + + box: object + """Search options for Box""" + + collections: object + """Search options for vault""" + + google_calendar: FilterGoogleCalendar + """Search options for Google Calendar""" + + google_drive: object + """Search options for Google Drive""" + + google_mail: FilterGoogleMail + """Search options for Gmail""" + + notion: FilterNotion + """Search options for Notion""" + + reddit: FilterReddit + """Search options for Reddit""" + + slack: FilterSlack + """Search options for Slack""" + + web_crawler: FilterWebCrawler + """Search options for Web Crawler""" + + +class OptionsGoogleCalendar(TypedDict, total=False): + calendar_id: Optional[str] + """The ID of the calendar to search. + + If not provided, it will use the ID of the default calendar. You can get the + list of calendars with the `/integrations/google_calendar/list` endpoint. + """ + + +class OptionsGoogleMail(TypedDict, total=False): + label_ids: List[str] + """List of label IDs to filter messages (e.g., ['INBOX', 'SENT', 'DRAFT']). + + Multiple labels are combined with OR logic - messages matching ANY specified + label will be returned. If empty, no label filtering is applied (searches all + accessible messages). + """ + + +class OptionsNotion(TypedDict, total=False): + notion_page_ids: List[str] + """List of Notion page IDs to search. + + If not provided, all pages in the workspace will be searched. + """ + + +class OptionsReddit(TypedDict, total=False): + period: Literal["hour", "day", "week", "month", "year", "all"] + """The time period to search. Defaults to 'month'.""" + + sort: Literal["relevance", "new", "hot", "top", "comments"] + """The sort order of the posts. Defaults to 'relevance'.""" + + subreddit: Optional[str] + """The subreddit to search. + + If not provided, the query will be searched for in all subreddits. + """ + + +class OptionsSlack(TypedDict, total=False): + channels: List[str] + """List of Slack channels to search. + + If not provided, all channels in the workspace will be searched. + """ + + +class OptionsWebCrawler(TypedDict, total=False): + max_depth: int + """Maximum depth to crawl from the starting URL""" + + url: Union[str, object] + """The URL to crawl""" + + +class Options(TypedDict, total=False): + after: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only query documents created on or after this date.""" + + answer_model: Literal["llama-3.1", "gemma2", "qwen-qwq", "mistral-saba", "llama-4-scout", "deepseek-r1"] + """Model to use for answer generation when answer=True""" + + before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] + """Only query documents created before this date.""" + + box: object + """Search options for Box""" + + collections: object + """Search options for vault""" + + google_calendar: OptionsGoogleCalendar + """Search options for Google Calendar""" + + google_drive: object + """Search options for Google Drive""" + + google_mail: OptionsGoogleMail + """Search options for Gmail""" + + notion: OptionsNotion + """Search options for Notion""" + + reddit: OptionsReddit + """Search options for Reddit""" + + slack: OptionsSlack + """Search options for Slack""" + + web_crawler: OptionsWebCrawler + """Search options for Web Crawler""" diff --git a/src/hyperspell/types/memory_search_response.py b/src/hyperspell/types/memory_search_response.py new file mode 100644 index 00000000..894b563a --- /dev/null +++ b/src/hyperspell/types/memory_search_response.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from .._models import BaseModel +from .document import Document + +__all__ = ["MemorySearchResponse"] + + +class MemorySearchResponse(BaseModel): + documents: List[Document] + + answer: Optional[str] = None + """The answer to the query, if the request was set to answer.""" + + errors: Optional[List[Dict[str, str]]] = None + """Errors that occurred during the query. + + These are meant to help the developer debug the query, and are not meant to be + shown to the user. + """ diff --git a/src/hyperspell/types/memory_status_response.py b/src/hyperspell/types/memory_status_response.py new file mode 100644 index 00000000..7e9bd79b --- /dev/null +++ b/src/hyperspell/types/memory_status_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict + +from .._models import BaseModel + +__all__ = ["MemoryStatusResponse"] + + +class MemoryStatusResponse(BaseModel): + providers: Dict[str, Dict[str, int]] + + total: Dict[str, int] diff --git a/src/hyperspell/types/memory_upload_params.py b/src/hyperspell/types/memory_upload_params.py new file mode 100644 index 00000000..51f93268 --- /dev/null +++ b/src/hyperspell/types/memory_upload_params.py @@ -0,0 +1,18 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import Required, TypedDict + +from .._types import FileTypes + +__all__ = ["MemoryUploadParams"] + + +class MemoryUploadParams(TypedDict, total=False): + file: Required[FileTypes] + """The file to ingest.""" + + collection: Optional[str] + """The collection to add the document to.""" diff --git a/src/hyperspell/types/vault_list_params.py b/src/hyperspell/types/vault_list_params.py new file mode 100644 index 00000000..357671f5 --- /dev/null +++ b/src/hyperspell/types/vault_list_params.py @@ -0,0 +1,14 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Optional +from typing_extensions import TypedDict + +__all__ = ["VaultListParams"] + + +class VaultListParams(TypedDict, total=False): + cursor: Optional[str] + + size: int diff --git a/src/hyperspell/types/vault_list_response.py b/src/hyperspell/types/vault_list_response.py new file mode 100644 index 00000000..35237ff5 --- /dev/null +++ b/src/hyperspell/types/vault_list_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from .._models import BaseModel + +__all__ = ["VaultListResponse"] + + +class VaultListResponse(BaseModel): + collection: Optional[str] = None + + document_count: int diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py new file mode 100644 index 00000000..4529672b --- /dev/null +++ b/tests/api_resources/test_memories.py @@ -0,0 +1,563 @@ +# 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 hyperspell import Hyperspell, AsyncHyperspell +from tests.utils import assert_matches_type +from hyperspell.types import ( + Document, + DocumentStatus, + MemorySearchResponse, + MemoryStatusResponse, +) +from hyperspell._utils import parse_datetime +from hyperspell.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestMemories: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Hyperspell) -> None: + memory = client.memories.list() + assert_matches_type(SyncCursorPage[Document], memory, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Hyperspell) -> None: + memory = client.memories.list( + collection="collection", + cursor="cursor", + size=0, + source="collections", + ) + assert_matches_type(SyncCursorPage[Document], memory, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Hyperspell) -> 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(SyncCursorPage[Document], memory, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Hyperspell) -> 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(SyncCursorPage[Document], memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_add(self, client: Hyperspell) -> None: + memory = client.memories.add( + text="text", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + def test_method_add_with_all_params(self, client: Hyperspell) -> None: + memory = client.memories.add( + text="text", + collection="collection", + date=parse_datetime("2019-12-27T18:11:19.117Z"), + resource_id="resource_id", + title="title", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + def test_raw_response_add(self, client: Hyperspell) -> None: + response = client.memories.with_raw_response.add( + text="text", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + def test_streaming_response_add(self, client: Hyperspell) -> None: + with client.memories.with_streaming_response.add( + text="text", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(DocumentStatus, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_get(self, client: Hyperspell) -> None: + memory = client.memories.get( + resource_id="resource_id", + source="collections", + ) + assert_matches_type(Document, memory, path=["response"]) + + @parametrize + def test_raw_response_get(self, client: Hyperspell) -> None: + response = client.memories.with_raw_response.get( + resource_id="resource_id", + source="collections", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(Document, memory, path=["response"]) + + @parametrize + def test_streaming_response_get(self, client: Hyperspell) -> None: + with client.memories.with_streaming_response.get( + resource_id="resource_id", + source="collections", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(Document, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_path_params_get(self, client: Hyperspell) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): + client.memories.with_raw_response.get( + resource_id="", + source="collections", + ) + + @parametrize + def test_method_search(self, client: Hyperspell) -> None: + memory = client.memories.search( + query="query", + ) + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + @parametrize + def test_method_search_with_all_params(self, client: Hyperspell) -> None: + memory = client.memories.search( + query="query", + answer=True, + filter={ + "after": parse_datetime("2019-12-27T18:11:19.117Z"), + "answer_model": "llama-3.1", + "before": parse_datetime("2019-12-27T18:11:19.117Z"), + "box": {}, + "collections": {}, + "google_calendar": {"calendar_id": "calendar_id"}, + "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, + "notion": {"notion_page_ids": ["string"]}, + "reddit": { + "period": "hour", + "sort": "relevance", + "subreddit": "subreddit", + }, + "slack": {"channels": ["string"]}, + "web_crawler": { + "max_depth": 0, + "url": "string", + }, + }, + max_results=0, + options={ + "after": parse_datetime("2019-12-27T18:11:19.117Z"), + "answer_model": "llama-3.1", + "before": parse_datetime("2019-12-27T18:11:19.117Z"), + "box": {}, + "collections": {}, + "google_calendar": {"calendar_id": "calendar_id"}, + "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, + "notion": {"notion_page_ids": ["string"]}, + "reddit": { + "period": "hour", + "sort": "relevance", + "subreddit": "subreddit", + }, + "slack": {"channels": ["string"]}, + "web_crawler": { + "max_depth": 0, + "url": "string", + }, + }, + sources=["collections"], + ) + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + @parametrize + def test_raw_response_search(self, client: Hyperspell) -> None: + response = client.memories.with_raw_response.search( + query="query", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + @parametrize + def test_streaming_response_search(self, client: Hyperspell) -> None: + with client.memories.with_streaming_response.search( + query="query", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_status(self, client: Hyperspell) -> None: + memory = client.memories.status() + assert_matches_type(MemoryStatusResponse, memory, path=["response"]) + + @parametrize + def test_raw_response_status(self, client: Hyperspell) -> None: + response = client.memories.with_raw_response.status() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert_matches_type(MemoryStatusResponse, memory, path=["response"]) + + @parametrize + def test_streaming_response_status(self, client: Hyperspell) -> None: + with client.memories.with_streaming_response.status() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert_matches_type(MemoryStatusResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + def test_method_upload(self, client: Hyperspell) -> None: + memory = client.memories.upload( + file=b"raw file contents", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + def test_method_upload_with_all_params(self, client: Hyperspell) -> None: + memory = client.memories.upload( + file=b"raw file contents", + collection="collection", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + def test_raw_response_upload(self, client: Hyperspell) -> None: + response = client.memories.with_raw_response.upload( + 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(DocumentStatus, memory, path=["response"]) + + @parametrize + def test_streaming_response_upload(self, client: Hyperspell) -> None: + with client.memories.with_streaming_response.upload( + 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(DocumentStatus, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncMemories: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.list() + assert_matches_type(AsyncCursorPage[Document], memory, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.list( + collection="collection", + cursor="cursor", + size=0, + source="collections", + ) + assert_matches_type(AsyncCursorPage[Document], memory, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncHyperspell) -> 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(AsyncCursorPage[Document], memory, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> 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(AsyncCursorPage[Document], memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_add(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.add( + text="text", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + async def test_method_add_with_all_params(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.add( + text="text", + collection="collection", + date=parse_datetime("2019-12-27T18:11:19.117Z"), + resource_id="resource_id", + title="title", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + async def test_raw_response_add(self, async_client: AsyncHyperspell) -> None: + response = await async_client.memories.with_raw_response.add( + text="text", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + async def test_streaming_response_add(self, async_client: AsyncHyperspell) -> None: + async with async_client.memories.with_streaming_response.add( + text="text", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(DocumentStatus, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_get(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.get( + resource_id="resource_id", + source="collections", + ) + assert_matches_type(Document, memory, path=["response"]) + + @parametrize + async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None: + response = await async_client.memories.with_raw_response.get( + resource_id="resource_id", + source="collections", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(Document, memory, path=["response"]) + + @parametrize + async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> None: + async with async_client.memories.with_streaming_response.get( + resource_id="resource_id", + source="collections", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(Document, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_path_params_get(self, async_client: AsyncHyperspell) -> None: + with pytest.raises(ValueError, match=r"Expected a non-empty value for `resource_id` but received ''"): + await async_client.memories.with_raw_response.get( + resource_id="", + source="collections", + ) + + @parametrize + async def test_method_search(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.search( + query="query", + ) + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + @parametrize + async def test_method_search_with_all_params(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.search( + query="query", + answer=True, + filter={ + "after": parse_datetime("2019-12-27T18:11:19.117Z"), + "answer_model": "llama-3.1", + "before": parse_datetime("2019-12-27T18:11:19.117Z"), + "box": {}, + "collections": {}, + "google_calendar": {"calendar_id": "calendar_id"}, + "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, + "notion": {"notion_page_ids": ["string"]}, + "reddit": { + "period": "hour", + "sort": "relevance", + "subreddit": "subreddit", + }, + "slack": {"channels": ["string"]}, + "web_crawler": { + "max_depth": 0, + "url": "string", + }, + }, + max_results=0, + options={ + "after": parse_datetime("2019-12-27T18:11:19.117Z"), + "answer_model": "llama-3.1", + "before": parse_datetime("2019-12-27T18:11:19.117Z"), + "box": {}, + "collections": {}, + "google_calendar": {"calendar_id": "calendar_id"}, + "google_drive": {}, + "google_mail": {"label_ids": ["string"]}, + "notion": {"notion_page_ids": ["string"]}, + "reddit": { + "period": "hour", + "sort": "relevance", + "subreddit": "subreddit", + }, + "slack": {"channels": ["string"]}, + "web_crawler": { + "max_depth": 0, + "url": "string", + }, + }, + sources=["collections"], + ) + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + @parametrize + async def test_raw_response_search(self, async_client: AsyncHyperspell) -> None: + response = await async_client.memories.with_raw_response.search( + query="query", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + @parametrize + async def test_streaming_response_search(self, async_client: AsyncHyperspell) -> None: + async with async_client.memories.with_streaming_response.search( + query="query", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(MemorySearchResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_status(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.status() + assert_matches_type(MemoryStatusResponse, memory, path=["response"]) + + @parametrize + async def test_raw_response_status(self, async_client: AsyncHyperspell) -> None: + response = await async_client.memories.with_raw_response.status() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert_matches_type(MemoryStatusResponse, memory, path=["response"]) + + @parametrize + async def test_streaming_response_status(self, async_client: AsyncHyperspell) -> None: + async with async_client.memories.with_streaming_response.status() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert_matches_type(MemoryStatusResponse, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @parametrize + async def test_method_upload(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.upload( + file=b"raw file contents", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + async def test_method_upload_with_all_params(self, async_client: AsyncHyperspell) -> None: + memory = await async_client.memories.upload( + file=b"raw file contents", + collection="collection", + ) + assert_matches_type(DocumentStatus, memory, path=["response"]) + + @parametrize + async def test_raw_response_upload(self, async_client: AsyncHyperspell) -> None: + response = await async_client.memories.with_raw_response.upload( + 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(DocumentStatus, memory, path=["response"]) + + @parametrize + async def test_streaming_response_upload(self, async_client: AsyncHyperspell) -> None: + async with async_client.memories.with_streaming_response.upload( + 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(DocumentStatus, memory, path=["response"]) + + assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/test_vaults.py b/tests/api_resources/test_vaults.py new file mode 100644 index 00000000..c0e4ab86 --- /dev/null +++ b/tests/api_resources/test_vaults.py @@ -0,0 +1,91 @@ +# 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 hyperspell import Hyperspell, AsyncHyperspell +from tests.utils import assert_matches_type +from hyperspell.types import VaultListResponse +from hyperspell.pagination import SyncCursorPage, AsyncCursorPage + +base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") + + +class TestVaults: + parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) + + @parametrize + def test_method_list(self, client: Hyperspell) -> None: + vault = client.vaults.list() + assert_matches_type(SyncCursorPage[VaultListResponse], vault, path=["response"]) + + @parametrize + def test_method_list_with_all_params(self, client: Hyperspell) -> None: + vault = client.vaults.list( + cursor="cursor", + size=0, + ) + assert_matches_type(SyncCursorPage[VaultListResponse], vault, path=["response"]) + + @parametrize + def test_raw_response_list(self, client: Hyperspell) -> None: + response = client.vaults.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + vault = response.parse() + assert_matches_type(SyncCursorPage[VaultListResponse], vault, path=["response"]) + + @parametrize + def test_streaming_response_list(self, client: Hyperspell) -> None: + with client.vaults.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + vault = response.parse() + assert_matches_type(SyncCursorPage[VaultListResponse], vault, path=["response"]) + + assert cast(Any, response.is_closed) is True + + +class TestAsyncVaults: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + + @parametrize + async def test_method_list(self, async_client: AsyncHyperspell) -> None: + vault = await async_client.vaults.list() + assert_matches_type(AsyncCursorPage[VaultListResponse], vault, path=["response"]) + + @parametrize + async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None: + vault = await async_client.vaults.list( + cursor="cursor", + size=0, + ) + assert_matches_type(AsyncCursorPage[VaultListResponse], vault, path=["response"]) + + @parametrize + async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None: + response = await async_client.vaults.with_raw_response.list() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + vault = await response.parse() + assert_matches_type(AsyncCursorPage[VaultListResponse], vault, path=["response"]) + + @parametrize + async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None: + async with async_client.vaults.with_streaming_response.list() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + vault = await response.parse() + assert_matches_type(AsyncCursorPage[VaultListResponse], vault, path=["response"]) + + assert cast(Any, response.is_closed) is True From 21036b0a3b626fa41925b58b24616484158b9361 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:18:16 +0000 Subject: [PATCH 30/33] feat(api): update via SDK Studio --- .stats.yml | 2 +- README.md | 113 +++++++++++++++++++++++++++++++++++-------- tests/test_client.py | 48 +++++++++--------- 3 files changed, 115 insertions(+), 48 deletions(-) diff --git a/.stats.yml b/.stats.yml index 798d9204..55bfe829 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-4bfa5ab6a0021f526d6f6f622f1872f2ec3467b6d1bd51acfc17bfc6f399f915.yml openapi_spec_hash: 5b51df5010bab70a1f3f884552ce25c3 -config_hash: 07a0a7ad518dab7df0aed6882f9d760b +config_hash: a76aa77ff531e4c950e278da93f5ee67 diff --git a/README.md b/README.md index d6e40d8a..67606142 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ client = Hyperspell( api_key=os.environ.get("HYPERSPELL_TOKEN"), # This is the default and can be omitted ) -response = client.integrations.revoke( - "provider", +document_status = client.memories.add( + text="text", ) -print(response.message) +print(document_status.id) ``` While you can provide an `api_key` keyword argument, @@ -58,10 +58,10 @@ client = AsyncHyperspell( async def main() -> None: - response = await client.integrations.revoke( - "provider", + document_status = await client.memories.add( + text="text", ) - print(response.message) + print(document_status.id) asyncio.run(main()) @@ -93,10 +93,10 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - response = await client.integrations.revoke( - "provider", + document_status = await client.memories.add( + text="text", ) - print(response.message) + print(document_status.id) asyncio.run(main()) @@ -111,6 +111,77 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`. +## Pagination + +List methods in the Hyperspell API are paginated. + +This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually: + +```python +from hyperspell import Hyperspell + +client = Hyperspell() + +all_memories = [] +# Automatically fetches more pages as needed. +for memory in client.memories.list( + collection="REPLACE_ME", +): + # Do something with memory here + all_memories.append(memory) +print(all_memories) +``` + +Or, asynchronously: + +```python +import asyncio +from hyperspell import AsyncHyperspell + +client = AsyncHyperspell() + + +async def main() -> None: + all_memories = [] + # Iterate through items across all pages, issuing requests as needed. + async for memory in client.memories.list( + collection="REPLACE_ME", + ): + all_memories.append(memory) + print(all_memories) + + +asyncio.run(main()) +``` + +Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages: + +```python +first_page = await client.memories.list( + collection="REPLACE_ME", +) +if first_page.has_next_page(): + print(f"will fetch next page using these details: {first_page.next_page_info()}") + next_page = await first_page.get_next_page() + print(f"number of items we just fetched: {len(next_page.items)}") + +# Remove `await` for non-async usage. +``` + +Or just work directly with the returned data: + +```python +first_page = await client.memories.list( + collection="REPLACE_ME", +) + +print(f"next page cursor: {first_page.next_cursor}") # => "next page cursor: ..." +for memory in first_page.items: + print(memory.resource_id) + +# Remove `await` for non-async usage. +``` + ## Nested params Nested parameters are dictionaries, typed using `TypedDict`, for example: @@ -160,8 +231,8 @@ from hyperspell import Hyperspell client = Hyperspell() try: - client.integrations.revoke( - "provider", + client.memories.add( + text="text", ) except hyperspell.APIConnectionError as e: print("The server could not be reached") @@ -205,8 +276,8 @@ client = Hyperspell( ) # Or, configure per-request: -client.with_options(max_retries=5).integrations.revoke( - "provider", +client.with_options(max_retries=5).memories.add( + text="text", ) ``` @@ -230,8 +301,8 @@ client = Hyperspell( ) # Override per-request: -client.with_options(timeout=5.0).integrations.revoke( - "provider", +client.with_options(timeout=5.0).memories.add( + text="text", ) ``` @@ -273,13 +344,13 @@ The "raw" Response object can be accessed by prefixing `.with_raw_response.` to from hyperspell import Hyperspell client = Hyperspell() -response = client.integrations.with_raw_response.revoke( - "provider", +response = client.memories.with_raw_response.add( + text="text", ) print(response.headers.get('X-My-Header')) -integration = response.parse() # get the object that `integrations.revoke()` would have returned -print(integration.message) +memory = response.parse() # get the object that `memories.add()` would have returned +print(memory.id) ``` These methods return an [`APIResponse`](https://github.com/hyperspell/python-sdk/tree/main/src/hyperspell/_response.py) object. @@ -293,8 +364,8 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.integrations.with_streaming_response.revoke( - "provider", +with client.memories.with_streaming_response.add( + text="text", ) as response: print(response.headers.get("X-My-Header")) diff --git a/tests/test_client.py b/tests/test_client.py index 16015957..8ddca0cf 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -783,20 +783,20 @@ def test_parse_retry_after_header(self, remaining_retries: int, retry_after: str @mock.patch("hyperspell._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, client: Hyperspell) -> None: - respx_mock.get("/integrations/provider/revoke").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/memories/add").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.integrations.with_streaming_response.revoke("provider").__enter__() + client.memories.with_streaming_response.add(text="text").__enter__() assert _get_open_connections(self.client) == 0 @mock.patch("hyperspell._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, client: Hyperspell) -> None: - respx_mock.get("/integrations/provider/revoke").mock(return_value=httpx.Response(500)) + respx_mock.post("/memories/add").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.integrations.with_streaming_response.revoke("provider").__enter__() + client.memories.with_streaming_response.add(text="text").__enter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -823,9 +823,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) + respx_mock.post("/memories/add").mock(side_effect=retry_handler) - response = client.integrations.with_raw_response.revoke("provider") + response = client.memories.with_raw_response.add(text="text") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -847,11 +847,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) + respx_mock.post("/memories/add").mock(side_effect=retry_handler) - response = client.integrations.with_raw_response.revoke( - "provider", extra_headers={"x-stainless-retry-count": Omit()} - ) + response = client.memories.with_raw_response.add(text="text", extra_headers={"x-stainless-retry-count": Omit()}) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -872,11 +870,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) + respx_mock.post("/memories/add").mock(side_effect=retry_handler) - response = client.integrations.with_raw_response.revoke( - "provider", extra_headers={"x-stainless-retry-count": "42"} - ) + response = client.memories.with_raw_response.add(text="text", extra_headers={"x-stainless-retry-count": "42"}) assert response.http_request.headers.get("x-stainless-retry-count") == "42" @@ -1664,10 +1660,10 @@ async def test_parse_retry_after_header(self, remaining_retries: int, retry_afte async def test_retrying_timeout_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncHyperspell ) -> None: - respx_mock.get("/integrations/provider/revoke").mock(side_effect=httpx.TimeoutException("Test timeout error")) + respx_mock.post("/memories/add").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.integrations.with_streaming_response.revoke("provider").__aenter__() + await async_client.memories.with_streaming_response.add(text="text").__aenter__() assert _get_open_connections(self.client) == 0 @@ -1676,10 +1672,10 @@ async def test_retrying_timeout_errors_doesnt_leak( async def test_retrying_status_errors_doesnt_leak( self, respx_mock: MockRouter, async_client: AsyncHyperspell ) -> None: - respx_mock.get("/integrations/provider/revoke").mock(return_value=httpx.Response(500)) + respx_mock.post("/memories/add").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.integrations.with_streaming_response.revoke("provider").__aenter__() + await async_client.memories.with_streaming_response.add(text="text").__aenter__() assert _get_open_connections(self.client) == 0 @pytest.mark.parametrize("failures_before_success", [0, 2, 4]) @@ -1707,9 +1703,9 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) + respx_mock.post("/memories/add").mock(side_effect=retry_handler) - response = await client.integrations.with_raw_response.revoke("provider") + response = await client.memories.with_raw_response.add(text="text") assert response.retries_taken == failures_before_success assert int(response.http_request.headers.get("x-stainless-retry-count")) == failures_before_success @@ -1732,10 +1728,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) + respx_mock.post("/memories/add").mock(side_effect=retry_handler) - response = await client.integrations.with_raw_response.revoke( - "provider", extra_headers={"x-stainless-retry-count": Omit()} + response = await client.memories.with_raw_response.add( + text="text", extra_headers={"x-stainless-retry-count": Omit()} ) assert len(response.http_request.headers.get_list("x-stainless-retry-count")) == 0 @@ -1758,10 +1754,10 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: return httpx.Response(500) return httpx.Response(200) - respx_mock.get("/integrations/provider/revoke").mock(side_effect=retry_handler) + respx_mock.post("/memories/add").mock(side_effect=retry_handler) - response = await client.integrations.with_raw_response.revoke( - "provider", extra_headers={"x-stainless-retry-count": "42"} + response = await client.memories.with_raw_response.add( + text="text", extra_headers={"x-stainless-retry-count": "42"} ) assert response.http_request.headers.get("x-stainless-retry-count") == "42" From 1e53d21ef72217cd2b90d39e65f0589f91205ef1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:19:55 +0000 Subject: [PATCH 31/33] codegen metadata --- .stats.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.stats.yml b/.stats.yml index 55bfe829..8a65b8b1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-4bfa5ab6a0021f526d6f6f622f1872f2ec3467b6d1bd51acfc17bfc6f399f915.yml openapi_spec_hash: 5b51df5010bab70a1f3f884552ce25c3 -config_hash: a76aa77ff531e4c950e278da93f5ee67 +config_hash: 22d90fc758c675b134ffcbbadd4b9be1 From 20bf1bbf36b4b5aa984cd6f405eb98fb6a743dba Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:20:31 +0000 Subject: [PATCH 32/33] feat(api): update via SDK Studio --- .stats.yml | 2 +- README.md | 12 ++-- api.md | 10 +-- src/hyperspell/resources/memories.py | 40 ++++++------ src/hyperspell/types/__init__.py | 4 +- .../types/{document.py => memory.py} | 4 +- .../types/memory_search_response.py | 4 +- .../{document_status.py => memory_status.py} | 4 +- tests/api_resources/test_memories.py | 64 +++++++++---------- 9 files changed, 72 insertions(+), 72 deletions(-) rename src/hyperspell/types/{document.py => memory.py} (96%) rename src/hyperspell/types/{document_status.py => memory_status.py} (95%) diff --git a/.stats.yml b/.stats.yml index 8a65b8b1..2c00fa68 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 12 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-4bfa5ab6a0021f526d6f6f622f1872f2ec3467b6d1bd51acfc17bfc6f399f915.yml openapi_spec_hash: 5b51df5010bab70a1f3f884552ce25c3 -config_hash: 22d90fc758c675b134ffcbbadd4b9be1 +config_hash: c894437241b21cedd2d01854f1c7a8ef diff --git a/README.md b/README.md index 67606142..5890113e 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,10 @@ client = Hyperspell( api_key=os.environ.get("HYPERSPELL_TOKEN"), # This is the default and can be omitted ) -document_status = client.memories.add( +memory_status = client.memories.add( text="text", ) -print(document_status.id) +print(memory_status.id) ``` While you can provide an `api_key` keyword argument, @@ -58,10 +58,10 @@ client = AsyncHyperspell( async def main() -> None: - document_status = await client.memories.add( + memory_status = await client.memories.add( text="text", ) - print(document_status.id) + print(memory_status.id) asyncio.run(main()) @@ -93,10 +93,10 @@ async def main() -> None: api_key="My API Key", http_client=DefaultAioHttpClient(), ) as client: - document_status = await client.memories.add( + memory_status = await client.memories.add( text="text", ) - print(document_status.id) + print(memory_status.id) asyncio.run(main()) diff --git a/api.md b/api.md index d6e8f501..bba49d1a 100644 --- a/api.md +++ b/api.md @@ -39,17 +39,17 @@ Methods: Types: ```python -from hyperspell.types import Document, DocumentStatus, MemorySearchResponse, MemoryStatusResponse +from hyperspell.types import Memory, MemoryStatus, MemorySearchResponse, MemoryStatusResponse ``` Methods: -- client.memories.list(\*\*params) -> SyncCursorPage[Document] -- client.memories.add(\*\*params) -> DocumentStatus -- client.memories.get(resource_id, \*, source) -> Document +- client.memories.list(\*\*params) -> SyncCursorPage[Memory] +- client.memories.add(\*\*params) -> MemoryStatus +- client.memories.get(resource_id, \*, source) -> Memory - client.memories.search(\*\*params) -> MemorySearchResponse - client.memories.status() -> MemoryStatusResponse -- client.memories.upload(\*\*params) -> DocumentStatus +- client.memories.upload(\*\*params) -> MemoryStatus # Vaults diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py index 94635353..94ed512e 100644 --- a/src/hyperspell/resources/memories.py +++ b/src/hyperspell/resources/memories.py @@ -21,8 +21,8 @@ ) from ..pagination import SyncCursorPage, AsyncCursorPage from .._base_client import AsyncPaginator, make_request_options -from ..types.document import Document -from ..types.document_status import DocumentStatus +from ..types.memory import Memory +from ..types.memory_status import MemoryStatus from ..types.memory_search_response import MemorySearchResponse from ..types.memory_status_response import MemoryStatusResponse @@ -112,7 +112,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> SyncCursorPage[Document]: + ) -> SyncCursorPage[Memory]: """This endpoint allows you to paginate through all documents in the index. You can @@ -133,7 +133,7 @@ def list( """ return self._get_api_list( "/memories/list", - page=SyncCursorPage[Document], + page=SyncCursorPage[Memory], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -149,7 +149,7 @@ def list( memory_list_params.MemoryListParams, ), ), - model=Document, + model=Memory, ) def add( @@ -166,7 +166,7 @@ def add( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> DocumentStatus: + ) -> MemoryStatus: """Adds an arbitrary document to the index. This can be any text, email, call @@ -211,7 +211,7 @@ def add( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=DocumentStatus, + cast_to=MemoryStatus, ) def get( @@ -272,7 +272,7 @@ def get( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Document: + ) -> Memory: """ Retrieves a document by provider and resource_id. @@ -294,7 +294,7 @@ def get( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Document, + cast_to=Memory, ) def search( @@ -439,7 +439,7 @@ def upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> DocumentStatus: + ) -> MemoryStatus: """This endpoint will upload a file to the index and return a document ID. The file @@ -478,7 +478,7 @@ def upload( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=DocumentStatus, + cast_to=MemoryStatus, ) @@ -565,7 +565,7 @@ def list( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> AsyncPaginator[Document, AsyncCursorPage[Document]]: + ) -> AsyncPaginator[Memory, AsyncCursorPage[Memory]]: """This endpoint allows you to paginate through all documents in the index. You can @@ -586,7 +586,7 @@ def list( """ return self._get_api_list( "/memories/list", - page=AsyncCursorPage[Document], + page=AsyncCursorPage[Memory], options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, @@ -602,7 +602,7 @@ def list( memory_list_params.MemoryListParams, ), ), - model=Document, + model=Memory, ) async def add( @@ -619,7 +619,7 @@ async def add( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> DocumentStatus: + ) -> MemoryStatus: """Adds an arbitrary document to the index. This can be any text, email, call @@ -664,7 +664,7 @@ async def add( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=DocumentStatus, + cast_to=MemoryStatus, ) async def get( @@ -725,7 +725,7 @@ async def get( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> Document: + ) -> Memory: """ Retrieves a document by provider and resource_id. @@ -747,7 +747,7 @@ async def get( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=Document, + cast_to=Memory, ) async def search( @@ -892,7 +892,7 @@ async def upload( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN, - ) -> DocumentStatus: + ) -> MemoryStatus: """This endpoint will upload a file to the index and return a document ID. The file @@ -931,7 +931,7 @@ async def upload( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=DocumentStatus, + cast_to=MemoryStatus, ) diff --git a/src/hyperspell/types/__init__.py b/src/hyperspell/types/__init__.py index 99655381..bfaf01aa 100644 --- a/src/hyperspell/types/__init__.py +++ b/src/hyperspell/types/__init__.py @@ -3,8 +3,8 @@ from __future__ import annotations from .token import Token as Token -from .document import Document as Document -from .document_status import DocumentStatus as DocumentStatus +from .memory import Memory as Memory +from .memory_status import MemoryStatus as MemoryStatus from .auth_me_response import AuthMeResponse as AuthMeResponse from .memory_add_params import MemoryAddParams as MemoryAddParams from .vault_list_params import VaultListParams as VaultListParams diff --git a/src/hyperspell/types/document.py b/src/hyperspell/types/memory.py similarity index 96% rename from src/hyperspell/types/document.py rename to src/hyperspell/types/memory.py index 6d22a128..0b9b3fe7 100644 --- a/src/hyperspell/types/document.py +++ b/src/hyperspell/types/memory.py @@ -6,7 +6,7 @@ from .._models import BaseModel -__all__ = ["Document", "Metadata", "MetadataEvent"] +__all__ = ["Memory", "Metadata", "MetadataEvent"] class MetadataEvent(BaseModel): @@ -33,7 +33,7 @@ class Metadata(BaseModel): def __getattr__(self, attr: str) -> object: ... -class Document(BaseModel): +class Memory(BaseModel): resource_id: str source: Literal[ diff --git a/src/hyperspell/types/memory_search_response.py b/src/hyperspell/types/memory_search_response.py index 894b563a..bf6410e8 100644 --- a/src/hyperspell/types/memory_search_response.py +++ b/src/hyperspell/types/memory_search_response.py @@ -2,14 +2,14 @@ from typing import Dict, List, Optional +from .memory import Memory from .._models import BaseModel -from .document import Document __all__ = ["MemorySearchResponse"] class MemorySearchResponse(BaseModel): - documents: List[Document] + documents: List[Memory] answer: Optional[str] = None """The answer to the query, if the request was set to answer.""" diff --git a/src/hyperspell/types/document_status.py b/src/hyperspell/types/memory_status.py similarity index 95% rename from src/hyperspell/types/document_status.py rename to src/hyperspell/types/memory_status.py index 0fb15e5a..6d061f77 100644 --- a/src/hyperspell/types/document_status.py +++ b/src/hyperspell/types/memory_status.py @@ -4,10 +4,10 @@ from .._models import BaseModel -__all__ = ["DocumentStatus"] +__all__ = ["MemoryStatus"] -class DocumentStatus(BaseModel): +class MemoryStatus(BaseModel): id: int """Deprecated: refer to documents by source and resource_id instead""" diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index 4529672b..b08ba4a7 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -10,8 +10,8 @@ from hyperspell import Hyperspell, AsyncHyperspell from tests.utils import assert_matches_type from hyperspell.types import ( - Document, - DocumentStatus, + Memory, + MemoryStatus, MemorySearchResponse, MemoryStatusResponse, ) @@ -27,7 +27,7 @@ class TestMemories: @parametrize def test_method_list(self, client: Hyperspell) -> None: memory = client.memories.list() - assert_matches_type(SyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(SyncCursorPage[Memory], memory, path=["response"]) @parametrize def test_method_list_with_all_params(self, client: Hyperspell) -> None: @@ -37,7 +37,7 @@ def test_method_list_with_all_params(self, client: Hyperspell) -> None: size=0, source="collections", ) - assert_matches_type(SyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(SyncCursorPage[Memory], memory, path=["response"]) @parametrize def test_raw_response_list(self, client: Hyperspell) -> None: @@ -46,7 +46,7 @@ def test_raw_response_list(self, client: Hyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(SyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(SyncCursorPage[Memory], memory, path=["response"]) @parametrize def test_streaming_response_list(self, client: Hyperspell) -> None: @@ -55,7 +55,7 @@ def test_streaming_response_list(self, client: Hyperspell) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(SyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(SyncCursorPage[Memory], memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -64,7 +64,7 @@ def test_method_add(self, client: Hyperspell) -> None: memory = client.memories.add( text="text", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize def test_method_add_with_all_params(self, client: Hyperspell) -> None: @@ -75,7 +75,7 @@ def test_method_add_with_all_params(self, client: Hyperspell) -> None: resource_id="resource_id", title="title", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize def test_raw_response_add(self, client: Hyperspell) -> None: @@ -86,7 +86,7 @@ def test_raw_response_add(self, client: Hyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize def test_streaming_response_add(self, client: Hyperspell) -> None: @@ -97,7 +97,7 @@ def test_streaming_response_add(self, client: Hyperspell) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -107,7 +107,7 @@ def test_method_get(self, client: Hyperspell) -> None: resource_id="resource_id", source="collections", ) - assert_matches_type(Document, memory, path=["response"]) + assert_matches_type(Memory, memory, path=["response"]) @parametrize def test_raw_response_get(self, client: Hyperspell) -> None: @@ -119,7 +119,7 @@ def test_raw_response_get(self, client: Hyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(Document, memory, path=["response"]) + assert_matches_type(Memory, memory, path=["response"]) @parametrize def test_streaming_response_get(self, client: Hyperspell) -> None: @@ -131,7 +131,7 @@ def test_streaming_response_get(self, client: Hyperspell) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(Document, memory, path=["response"]) + assert_matches_type(Memory, memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -256,7 +256,7 @@ def test_method_upload(self, client: Hyperspell) -> None: memory = client.memories.upload( file=b"raw file contents", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize def test_method_upload_with_all_params(self, client: Hyperspell) -> None: @@ -264,7 +264,7 @@ def test_method_upload_with_all_params(self, client: Hyperspell) -> None: file=b"raw file contents", collection="collection", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize def test_raw_response_upload(self, client: Hyperspell) -> None: @@ -275,7 +275,7 @@ def test_raw_response_upload(self, client: Hyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize def test_streaming_response_upload(self, client: Hyperspell) -> None: @@ -286,7 +286,7 @@ def test_streaming_response_upload(self, client: Hyperspell) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -299,7 +299,7 @@ class TestAsyncMemories: @parametrize async def test_method_list(self, async_client: AsyncHyperspell) -> None: memory = await async_client.memories.list() - assert_matches_type(AsyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(AsyncCursorPage[Memory], memory, path=["response"]) @parametrize async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) -> None: @@ -309,7 +309,7 @@ async def test_method_list_with_all_params(self, async_client: AsyncHyperspell) size=0, source="collections", ) - assert_matches_type(AsyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(AsyncCursorPage[Memory], memory, path=["response"]) @parametrize async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None: @@ -318,7 +318,7 @@ async def test_raw_response_list(self, async_client: AsyncHyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(AsyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(AsyncCursorPage[Memory], memory, path=["response"]) @parametrize async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> None: @@ -327,7 +327,7 @@ async def test_streaming_response_list(self, async_client: AsyncHyperspell) -> N assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(AsyncCursorPage[Document], memory, path=["response"]) + assert_matches_type(AsyncCursorPage[Memory], memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -336,7 +336,7 @@ async def test_method_add(self, async_client: AsyncHyperspell) -> None: memory = await async_client.memories.add( text="text", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize async def test_method_add_with_all_params(self, async_client: AsyncHyperspell) -> None: @@ -347,7 +347,7 @@ async def test_method_add_with_all_params(self, async_client: AsyncHyperspell) - resource_id="resource_id", title="title", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize async def test_raw_response_add(self, async_client: AsyncHyperspell) -> None: @@ -358,7 +358,7 @@ async def test_raw_response_add(self, async_client: AsyncHyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize async def test_streaming_response_add(self, async_client: AsyncHyperspell) -> None: @@ -369,7 +369,7 @@ async def test_streaming_response_add(self, async_client: AsyncHyperspell) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -379,7 +379,7 @@ async def test_method_get(self, async_client: AsyncHyperspell) -> None: resource_id="resource_id", source="collections", ) - assert_matches_type(Document, memory, path=["response"]) + assert_matches_type(Memory, memory, path=["response"]) @parametrize async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None: @@ -391,7 +391,7 @@ async def test_raw_response_get(self, async_client: AsyncHyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(Document, memory, path=["response"]) + assert_matches_type(Memory, memory, path=["response"]) @parametrize async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> None: @@ -403,7 +403,7 @@ async def test_streaming_response_get(self, async_client: AsyncHyperspell) -> No assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(Document, memory, path=["response"]) + assert_matches_type(Memory, memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -528,7 +528,7 @@ async def test_method_upload(self, async_client: AsyncHyperspell) -> None: memory = await async_client.memories.upload( file=b"raw file contents", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize async def test_method_upload_with_all_params(self, async_client: AsyncHyperspell) -> None: @@ -536,7 +536,7 @@ async def test_method_upload_with_all_params(self, async_client: AsyncHyperspell file=b"raw file contents", collection="collection", ) - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize async def test_raw_response_upload(self, async_client: AsyncHyperspell) -> None: @@ -547,7 +547,7 @@ async def test_raw_response_upload(self, async_client: AsyncHyperspell) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) @parametrize async def test_streaming_response_upload(self, async_client: AsyncHyperspell) -> None: @@ -558,6 +558,6 @@ async def test_streaming_response_upload(self, async_client: AsyncHyperspell) -> assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(DocumentStatus, memory, path=["response"]) + assert_matches_type(MemoryStatus, memory, path=["response"]) assert cast(Any, response.is_closed) is True From 0b78ebf6e8c7e49725886be5cae028999b9c9dc8 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:21:00 +0000 Subject: [PATCH 33/33] release: 0.19.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/hyperspell/_version.py | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4ad3fef3..e7562934 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.18.0" + ".": "0.19.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e1a462e9..e3ee8045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## 0.19.0 (2025-07-18) + +Full Changelog: [v0.18.0...v0.19.0](https://github.com/hyperspell/python-sdk/compare/v0.18.0...v0.19.0) + +### Features + +* **api:** api update ([e4edf05](https://github.com/hyperspell/python-sdk/commit/e4edf05d2e2700fef1e70250204aee103ac54686)) +* **api:** api update ([7248ff5](https://github.com/hyperspell/python-sdk/commit/7248ff5db8bbc8708f8dfe8afb78d519401ee7f2)) +* **api:** update via SDK Studio ([20bf1bb](https://github.com/hyperspell/python-sdk/commit/20bf1bbf36b4b5aa984cd6f405eb98fb6a743dba)) +* **api:** update via SDK Studio ([21036b0](https://github.com/hyperspell/python-sdk/commit/21036b0a3b626fa41925b58b24616484158b9361)) +* **api:** update via SDK Studio ([bf294db](https://github.com/hyperspell/python-sdk/commit/bf294db204ddb25338ab2370d78d6c378c78686a)) +* clean up environment call outs ([e99844e](https://github.com/hyperspell/python-sdk/commit/e99844ee29928beeed51cd47d53e3ababe33e608)) +* **client:** add support for aiohttp ([c0845dc](https://github.com/hyperspell/python-sdk/commit/c0845dc8133eb74e72f18651e9da53102609d833)) + + +### Bug Fixes + +* **ci:** correct conditional ([980b910](https://github.com/hyperspell/python-sdk/commit/980b910eaa78cf84de10c0521e4ed2f142689ae0)) +* **ci:** release-doctor — report correct token name ([6ddb78b](https://github.com/hyperspell/python-sdk/commit/6ddb78b355f8e36a85fcfaf1592618bae5c67473)) +* **client:** don't send Content-Type header on GET requests ([f7a04b5](https://github.com/hyperspell/python-sdk/commit/f7a04b5a47aa9a6fc338498a34db4074824c198e)) +* **parsing:** correctly handle nested discriminated unions ([f4e309d](https://github.com/hyperspell/python-sdk/commit/f4e309d6a8d85f5aa0cb3fee3e41bf9c6487b5b1)) + + +### Chores + +* **ci:** change upload type ([c01d519](https://github.com/hyperspell/python-sdk/commit/c01d519131a2c4620c9454c4e634c2e39053b00c)) +* **ci:** only run for pushes and fork pull requests ([991d685](https://github.com/hyperspell/python-sdk/commit/991d6859286e86aa022eb83aab8ecc6e337798f4)) +* **internal:** bump pinned h11 dep ([1ce5781](https://github.com/hyperspell/python-sdk/commit/1ce57814e1d392b0c6a51b75c1dd92550f9399c3)) +* **internal:** codegen related update ([f7cd6c7](https://github.com/hyperspell/python-sdk/commit/f7cd6c7b1feafef059ecdf6be0bb9adbd5f28929)) +* **package:** mark python 3.13 as supported ([b04d1e6](https://github.com/hyperspell/python-sdk/commit/b04d1e6c7d2dbd8852963ee8f08596ed332e04ec)) +* **readme:** fix version rendering on pypi ([a109225](https://github.com/hyperspell/python-sdk/commit/a1092259c98715d17ce95259420baf3c3d325db8)) +* **tests:** skip some failing tests on the latest python versions ([9eebf62](https://github.com/hyperspell/python-sdk/commit/9eebf628b254b4f463ea7b899dcef53af5362228)) + ## 0.18.0 (2025-06-19) Full Changelog: [v0.16.0...v0.18.0](https://github.com/hyperspell/python-sdk/compare/v0.16.0...v0.18.0) diff --git a/pyproject.toml b/pyproject.toml index 46197115..b08762c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hyperspell" -version = "0.18.0" +version = "0.19.0" description = "The official Python library for the hyperspell API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/hyperspell/_version.py b/src/hyperspell/_version.py index df0b6d8e..60454a0d 100644 --- a/src/hyperspell/_version.py +++ b/src/hyperspell/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "hyperspell" -__version__ = "0.18.0" # x-release-please-version +__version__ = "0.19.0" # x-release-please-version