diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 53338790..1ff4a3ff 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -12,6 +12,9 @@ jobs: publish: name: publish runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - uses: actions/checkout@v6 @@ -27,5 +30,3 @@ jobs: - name: Publish to PyPI run: | bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.HYPERSPELL_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 7f739e2a..b7b5ce04 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -17,5 +17,3 @@ jobs: - name: Check release environment run: | bash ./bin/check-release-environment - env: - PYPI_TOKEN: ${{ secrets.HYPERSPELL_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 57dc0c3d..e4e1c3ce 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.33.0" + ".": "0.34.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 782ef1fc..51931843 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 23 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-36cb6e2474f3fe09749b7a2f24409d48c8db332d624fa7eeb1ee6b6135774133.yml -openapi_spec_hash: 339a1b55d6b1a55213d16bf336045d0d -config_hash: 983708fc30c86269c2149a960d0bfec1 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/hyperspell%2Fhyperspell-d6e895ab5ce17b403a1981c9f3e3e1a357d2016683627cbbc725c10f6aa2e13a.yml +openapi_spec_hash: 36fc6b210e87fbd995fd578adcbe6626 +config_hash: fd3005a8f140e5baadd3d25b3c9cd79f diff --git a/CHANGELOG.md b/CHANGELOG.md index 254432cc..e868dc57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## 0.34.0 (2026-03-07) + +Full Changelog: [v0.33.0...v0.34.0](https://github.com/hyperspell/python-sdk/compare/v0.33.0...v0.34.0) + +### Features + +* **api:** api update ([0f66c9d](https://github.com/hyperspell/python-sdk/commit/0f66c9dfe8e50d22f1a66f6de9b3ee0f1a89ab38)) +* **api:** api update ([b76b873](https://github.com/hyperspell/python-sdk/commit/b76b873e4c01f4c9421cc6e452f216c19374b687)) +* **api:** api update ([b75e2b3](https://github.com/hyperspell/python-sdk/commit/b75e2b3bbb5a871a39d147e4cb0949d7279f3271)) +* **api:** api update ([d330dea](https://github.com/hyperspell/python-sdk/commit/d330deaec6f907f4abee6dd8fc786da018ea72a7)) +* **api:** api update ([e01d911](https://github.com/hyperspell/python-sdk/commit/e01d911cb400b1ee3827332299f054d278f867d4)) + + +### Chores + +* **api:** python trusted publisher ([3d7a283](https://github.com/hyperspell/python-sdk/commit/3d7a283ebe54b78a23964a17a3167c50da0aeba2)) +* **internal:** add request options to SSE classes ([6b5c9ad](https://github.com/hyperspell/python-sdk/commit/6b5c9ad8271ecb8887faf8cb678805e6d481459c)) +* **internal:** make `test_proxy_environment_variables` more resilient ([3cc3332](https://github.com/hyperspell/python-sdk/commit/3cc33321d35a6162c6c8540b571d4d3c2b63a022)) +* **internal:** make `test_proxy_environment_variables` more resilient to env ([2c99a7f](https://github.com/hyperspell/python-sdk/commit/2c99a7f619b2581f2bf7bc2bea4519fc1f6c5733)) +* **test:** do not count install time for mock server timeout ([ee29761](https://github.com/hyperspell/python-sdk/commit/ee29761e756adb19eb1f07232abde4e2a1e20d4f)) + ## 0.33.0 (2026-02-20) Full Changelog: [v0.32.0...v0.33.0](https://github.com/hyperspell/python-sdk/compare/v0.32.0...v0.33.0) diff --git a/bin/check-release-environment b/bin/check-release-environment index b845b0f4..1e951e9a 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,10 +2,6 @@ errors=() -if [ -z "${PYPI_TOKEN}" ]; then - 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[@]} if [[ lenErrors -gt 0 ]]; then diff --git a/bin/publish-pypi b/bin/publish-pypi index 826054e9..4d74d121 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -3,4 +3,8 @@ set -eux mkdir -p dist rye build --clean -rye publish --yes --token=$PYPI_TOKEN +if [ -n "${PYPI_TOKEN:-}" ]; then + rye publish --yes --token=$PYPI_TOKEN +else + rye publish --yes +fi diff --git a/pyproject.toml b/pyproject.toml index 7a5607b4..5bc42897 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "hyperspell" -version = "0.33.0" +version = "0.34.0" description = "The official Python library for the hyperspell API" dynamic = ["readme"] license = "MIT" diff --git a/scripts/mock b/scripts/mock index 0b28f6ea..bcf3b392 100755 --- a/scripts/mock +++ b/scripts/mock @@ -21,11 +21,22 @@ echo "==> Starting mock server with URL ${URL}" # Run prism mock on the given spec if [ "$1" == "--daemon" ]; then + # Pre-install the package so the download doesn't eat into the startup timeout + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism --version + npm exec --package=@stainless-api/prism-cli@5.15.0 -- prism mock "$URL" &> .prism.log & - # Wait for server to come online + # Wait for server to come online (max 30s) echo -n "Waiting for server" + attempts=0 while ! grep -q "✖ fatal\|Prism is listening" ".prism.log" ; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge 300 ]; then + echo + echo "Timed out waiting for Prism server to start" + cat .prism.log + exit 1 + fi echo -n "." sleep 0.1 done diff --git a/src/hyperspell/_response.py b/src/hyperspell/_response.py index e1cbe16a..3de65894 100644 --- a/src/hyperspell/_response.py +++ b/src/hyperspell/_response.py @@ -152,6 +152,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: ), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -162,6 +163,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=extract_stream_chunk_type(self._stream_cls), response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) @@ -175,6 +177,7 @@ def _parse(self, *, to: type[_T] | None = None) -> R | _T: cast_to=cast_to, response=self.http_response, client=cast(Any, self._client), + options=self._options, ), ) diff --git a/src/hyperspell/_streaming.py b/src/hyperspell/_streaming.py index ec124810..b32811c5 100644 --- a/src/hyperspell/_streaming.py +++ b/src/hyperspell/_streaming.py @@ -4,7 +4,7 @@ import json import inspect from types import TracebackType -from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, AsyncIterator, cast +from typing import TYPE_CHECKING, Any, Generic, TypeVar, Iterator, Optional, AsyncIterator, cast from typing_extensions import Self, Protocol, TypeGuard, override, get_origin, runtime_checkable import httpx @@ -13,6 +13,7 @@ if TYPE_CHECKING: from ._client import Hyperspell, AsyncHyperspell + from ._models import FinalRequestOptions _T = TypeVar("_T") @@ -22,7 +23,7 @@ class Stream(Generic[_T]): """Provides the core interface to iterate over a synchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEBytesDecoder def __init__( @@ -31,6 +32,7 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: Hyperspell, + options: Optional[FinalRequestOptions] = None, timeout: float | None = None, ) -> None: """Initialize the synchronous stream. @@ -44,6 +46,7 @@ def __init__( self.response = response self._cast_to = cast_to self._client = client + self._options = options self._timeout = timeout self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() @@ -115,7 +118,7 @@ class AsyncStream(Generic[_T]): """Provides the core interface to iterate over an asynchronous stream response.""" response: httpx.Response - + _options: Optional[FinalRequestOptions] = None _decoder: SSEDecoder | SSEBytesDecoder def __init__( @@ -124,6 +127,7 @@ def __init__( cast_to: type[_T], response: httpx.Response, client: AsyncHyperspell, + options: Optional[FinalRequestOptions] = None, timeout: float | None = None, ) -> None: """Initialize the asynchronous stream. @@ -137,6 +141,7 @@ def __init__( self.response = response self._cast_to = cast_to self._client = client + self._options = options self._timeout = timeout self._decoder = client._make_sse_decoder() self._iterator = self.__stream__() diff --git a/src/hyperspell/_version.py b/src/hyperspell/_version.py index 778314bc..19fe74d1 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.33.0" # x-release-please-version +__version__ = "0.34.0" # x-release-please-version diff --git a/src/hyperspell/resources/memories.py b/src/hyperspell/resources/memories.py index 8665314f..e79e9132 100644 --- a/src/hyperspell/resources/memories.py +++ b/src/hyperspell/resources/memories.py @@ -72,8 +72,10 @@ def update( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ], collection: Union[str, object, None] | Omit = omit, metadata: Union[Dict[str, Union[str, float, bool, None]], object, None] | Omit = omit, @@ -152,8 +154,10 @@ def list( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] | Omit = omit, @@ -225,8 +229,10 @@ def delete( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ], # 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. @@ -395,8 +401,10 @@ def get( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ], # 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. @@ -446,8 +454,10 @@ def search( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] | Omit = omit, @@ -613,8 +623,10 @@ async def update( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ], collection: Union[str, object, None] | Omit = omit, metadata: Union[Dict[str, Union[str, float, bool, None]], object, None] | Omit = omit, @@ -693,8 +705,10 @@ def list( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] | Omit = omit, @@ -766,8 +780,10 @@ async def delete( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ], # 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. @@ -936,8 +952,10 @@ async def get( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ], # 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. @@ -987,8 +1005,10 @@ async def search( "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] | Omit = omit, diff --git a/src/hyperspell/types/auth_me_response.py b/src/hyperspell/types/auth_me_response.py index feeae525..d8f298f2 100644 --- a/src/hyperspell/types/auth_me_response.py +++ b/src/hyperspell/types/auth_me_response.py @@ -42,8 +42,10 @@ class AuthMeResponse(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] """All integrations available for the app""" @@ -58,8 +60,10 @@ class AuthMeResponse(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] """All integrations installed for the user""" diff --git a/src/hyperspell/types/connection_list_response.py b/src/hyperspell/types/connection_list_response.py index 6650045c..e43f8f63 100644 --- a/src/hyperspell/types/connection_list_response.py +++ b/src/hyperspell/types/connection_list_response.py @@ -27,8 +27,10 @@ class Connection(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] """The connection's provider""" diff --git a/src/hyperspell/types/integration_list_response.py b/src/hyperspell/types/integration_list_response.py index fa6ef1a2..4d590381 100644 --- a/src/hyperspell/types/integration_list_response.py +++ b/src/hyperspell/types/integration_list_response.py @@ -33,8 +33,10 @@ class Integration(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] """The integration's provider""" diff --git a/src/hyperspell/types/integrations/web_crawler_index_response.py b/src/hyperspell/types/integrations/web_crawler_index_response.py index 7e2a9a8c..cd76f8fc 100644 --- a/src/hyperspell/types/integrations/web_crawler_index_response.py +++ b/src/hyperspell/types/integrations/web_crawler_index_response.py @@ -19,8 +19,10 @@ class WebCrawlerIndexResponse(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] status: Literal["pending", "processing", "completed", "failed"] diff --git a/src/hyperspell/types/memory.py b/src/hyperspell/types/memory.py index 2e009f5d..89d61f15 100644 --- a/src/hyperspell/types/memory.py +++ b/src/hyperspell/types/memory.py @@ -25,8 +25,10 @@ class Memory(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] type: str diff --git a/src/hyperspell/types/memory_delete_response.py b/src/hyperspell/types/memory_delete_response.py index 47e97a6c..453054a3 100644 --- a/src/hyperspell/types/memory_delete_response.py +++ b/src/hyperspell/types/memory_delete_response.py @@ -23,8 +23,10 @@ class MemoryDeleteResponse(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] success: bool diff --git a/src/hyperspell/types/memory_list_params.py b/src/hyperspell/types/memory_list_params.py index 79d5cdba..910b4cb0 100644 --- a/src/hyperspell/types/memory_list_params.py +++ b/src/hyperspell/types/memory_list_params.py @@ -32,8 +32,10 @@ class MemoryListParams(TypedDict, total=False): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] """Filter documents by source.""" diff --git a/src/hyperspell/types/memory_list_response.py b/src/hyperspell/types/memory_list_response.py index 729bd3d6..704b1f19 100644 --- a/src/hyperspell/types/memory_list_response.py +++ b/src/hyperspell/types/memory_list_response.py @@ -21,8 +21,10 @@ class MemoryListResponse(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] metadata: Optional[Metadata] = None diff --git a/src/hyperspell/types/memory_search_params.py b/src/hyperspell/types/memory_search_params.py index e5c93348..9cdf7aad 100644 --- a/src/hyperspell/types/memory_search_params.py +++ b/src/hyperspell/types/memory_search_params.py @@ -47,8 +47,10 @@ class MemorySearchParams(TypedDict, total=False): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] """Only query documents from these sources.""" @@ -225,7 +227,9 @@ 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"] + answer_model: Literal[ + "llama-3.1", "gemma2", "qwen-qwq", "mistral-saba", "llama-4-scout", "deepseek-r1", "gpt-oss-20b", "gpt-oss-120b" + ] """Model to use for answer generation when answer=True""" before: Annotated[Union[str, datetime, None], PropertyInfo(format="iso8601")] @@ -258,6 +262,13 @@ class Options(TypedDict, total=False): reddit: OptionsReddit """Search options for Reddit""" + resource_ids: Optional[SequenceNotStr[str]] + """Only return results from these specific resource IDs. + + Useful for scoping searches to specific documents (e.g., a specific email thread + or uploaded file). + """ + slack: OptionsSlack """Search options for Slack""" diff --git a/src/hyperspell/types/memory_status.py b/src/hyperspell/types/memory_status.py index c9be10da..7a316e99 100644 --- a/src/hyperspell/types/memory_status.py +++ b/src/hyperspell/types/memory_status.py @@ -19,8 +19,10 @@ class MemoryStatus(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] status: Literal["pending", "processing", "completed", "failed"] diff --git a/src/hyperspell/types/memory_update_params.py b/src/hyperspell/types/memory_update_params.py index 2ceecd6a..ed3d2ca8 100644 --- a/src/hyperspell/types/memory_update_params.py +++ b/src/hyperspell/types/memory_update_params.py @@ -19,8 +19,10 @@ class MemoryUpdateParams(TypedDict, total=False): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] ] diff --git a/src/hyperspell/types/shared/query_result.py b/src/hyperspell/types/shared/query_result.py index c3e07b78..dd7bc7c6 100644 --- a/src/hyperspell/types/shared/query_result.py +++ b/src/hyperspell/types/shared/query_result.py @@ -21,8 +21,10 @@ class Document(BaseModel): "box", "dropbox", "google_drive", + "github", "vault", "web_crawler", + "trace", ] metadata: Optional[Metadata] = None diff --git a/tests/api_resources/test_memories.py b/tests/api_resources/test_memories.py index fb3fb951..784e3d0d 100644 --- a/tests/api_resources/test_memories.py +++ b/tests/api_resources/test_memories.py @@ -304,7 +304,7 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None: "label_ids": ["string"], "weight": 0, }, - "max_results": 0, + "max_results": 200, "notion": { "notion_page_ids": ["string"], "weight": 0, @@ -315,6 +315,7 @@ def test_method_search_with_all_params(self, client: Hyperspell) -> None: "subreddit": "subreddit", "weight": 0, }, + "resource_ids": ["string"], "slack": { "channels": ["string"], "exclude_archived": True, @@ -706,7 +707,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell "label_ids": ["string"], "weight": 0, }, - "max_results": 0, + "max_results": 200, "notion": { "notion_page_ids": ["string"], "weight": 0, @@ -717,6 +718,7 @@ async def test_method_search_with_all_params(self, async_client: AsyncHyperspell "subreddit": "subreddit", "weight": 0, }, + "resource_ids": ["string"], "slack": { "channels": ["string"], "exclude_archived": True, diff --git a/tests/test_client.py b/tests/test_client.py index f3aef279..fedd4853 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1019,6 +1019,14 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultHttpxClient() @@ -1998,6 +2006,14 @@ async def test_get_platform(self) -> None: async def test_proxy_environment_variables(self, monkeypatch: pytest.MonkeyPatch) -> None: # Test that the proxy environment variables are set correctly monkeypatch.setenv("HTTPS_PROXY", "https://example.org") + # Delete in case our environment has any proxy env vars set + monkeypatch.delenv("HTTP_PROXY", raising=False) + monkeypatch.delenv("ALL_PROXY", raising=False) + monkeypatch.delenv("NO_PROXY", raising=False) + monkeypatch.delenv("http_proxy", raising=False) + monkeypatch.delenv("https_proxy", raising=False) + monkeypatch.delenv("all_proxy", raising=False) + monkeypatch.delenv("no_proxy", raising=False) client = DefaultAsyncHttpxClient()