From 0fd8f323217273d9a22e0f6765621b6dc237ce7b Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 1 Sep 2026 14:58:34 +0000 Subject: [PATCH 1/4] feat: tpuf-engine: expose billed_replicas in pinning metadata --- .stats.yml | 4 ++-- src/turbopuffer/types/namespace_metadata.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.stats.yml b/.stats.yml index 85e2faf7..5a5a5e96 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/turbopuffer-benesch/turbopuffer-f382ef6b6b767722c39ffac9ef27b383f3192cdb180f1674f2969836c3cbda4f.yml -openapi_spec_hash: 96988a5a07baae1ac411e14b0eef8424 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/turbopuffer-benesch/turbopuffer-96c0d9aaf535d16273fa6e2ccb8e3107e5743e6d0aa82cfa2affc26be0e88d7b.yml +openapi_spec_hash: 4ecaba2c433cb2676b4eeba16b1bdc46 config_hash: e8f59500814fce6a9e1a14dc46e0dfc7 diff --git a/src/turbopuffer/types/namespace_metadata.py b/src/turbopuffer/types/namespace_metadata.py index fe7dfaaa..064d4d40 100644 --- a/src/turbopuffer/types/namespace_metadata.py +++ b/src/turbopuffer/types/namespace_metadata.py @@ -38,6 +38,14 @@ class PinningStatus(BaseModel): ready_replicas: int """The number of replicas that are warm and serving traffic.""" + replicas: int + """The number of running replicas for the namespace. + + Replicas are billed once running, even before they finish warming their caches + and become ready to serve traffic. This count is updated independently and may + briefly disagree with the other status fields. + """ + updated_at: datetime """The timestamp of the latest pinning status snapshot.""" From 9b4622df46ab1d4a8406b8e09e467991605538ce Mon Sep 17 00:00:00 2001 From: Nathan VanBenschoten Date: Wed, 2 Sep 2026 18:58:51 -0400 Subject: [PATCH 2/4] tests: accept 1.28GB query bytes floor (#261) CI against current servers still returns 256MB, while servers with the new pricing floor return 1.28GB. Co-authored-by: Cursor --- tests/custom/test_vectors.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/custom/test_vectors.py b/tests/custom/test_vectors.py index 5e3ed80c..75407a3e 100644 --- a/tests/custom/test_vectors.py +++ b/tests/custom/test_vectors.py @@ -10,7 +10,6 @@ from turbopuffer.types import ( Row, Vector, - QueryBilling, VectorEncoding, NamespaceWriteParams, NamespaceQueryResponse, @@ -203,10 +202,9 @@ def check_results(vector_set: NamespaceQueryResponse, expected: List[Row]): include_attributes=["hello", "vector"], ) check_results(vector_set, expected) - assert vector_set.billing == QueryBilling( - billable_logical_bytes_queried=256000000, - billable_logical_bytes_returned=105, - ) + assert vector_set.billing.billable_logical_bytes_returned == 105 + # 256MB was the old query floor; 1.28GB is current pricing. + assert vector_set.billing.billable_logical_bytes_queried in (256000000, 1280000000) # Test query with dict vector_set = ns.query( From 691d1ee1399f85e11cf37eff912f4f0b6fd5e83d Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:51:26 +0000 Subject: [PATCH 3/4] feat: Add `read_only` namespace field --- .stats.yml | 4 ++-- src/turbopuffer/resources/namespaces.py | 22 +++++++++++++++++-- src/turbopuffer/types/namespace_metadata.py | 3 +++ .../types/namespace_update_metadata_params.py | 6 +++++ tests/api_resources/test_namespaces.py | 2 ++ 5 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.stats.yml b/.stats.yml index 5a5a5e96..0be307d1 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 14 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/turbopuffer-benesch/turbopuffer-96c0d9aaf535d16273fa6e2ccb8e3107e5743e6d0aa82cfa2affc26be0e88d7b.yml -openapi_spec_hash: 4ecaba2c433cb2676b4eeba16b1bdc46 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/turbopuffer-benesch/turbopuffer-10add2e38f8fa0826d10992d6de7c4d51c42f2c862412b221527a5d08af43a22.yml +openapi_spec_hash: 9382f99c761306a6a1b943beb230a8d4 config_hash: e8f59500814fce6a9e1a14dc46e0dfc7 diff --git a/src/turbopuffer/resources/namespaces.py b/src/turbopuffer/resources/namespaces.py index c0dd4fb0..60c59533 100644 --- a/src/turbopuffer/resources/namespaces.py +++ b/src/turbopuffer/resources/namespaces.py @@ -644,6 +644,7 @@ def update_metadata( *, namespace: str | None = None, pinning: Optional[namespace_update_metadata_params.Pinning] | Omit = omit, + read_only: bool | Omit = omit, # 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, @@ -662,6 +663,9 @@ def update_metadata( - `true`: enable pinning with default configuration - Object: set pinning configuration + read_only: Set to `true` to reject document and schema writes, or `false` to allow them. + Writes already in progress may still commit. Metadata updates remain available. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -676,7 +680,13 @@ def update_metadata( raise ValueError(f"Expected a non-empty value for `namespace` but received {namespace!r}") return self._patch( path_template("/v1/namespaces/{namespace}/metadata", namespace=namespace), - body=maybe_transform({"pinning": pinning}, namespace_update_metadata_params.NamespaceUpdateMetadataParams), + body=maybe_transform( + { + "pinning": pinning, + "read_only": read_only, + }, + namespace_update_metadata_params.NamespaceUpdateMetadataParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -1430,6 +1440,7 @@ async def update_metadata( *, namespace: str | None = None, pinning: Optional[namespace_update_metadata_params.Pinning] | Omit = omit, + read_only: bool | Omit = omit, # 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, @@ -1448,6 +1459,9 @@ async def update_metadata( - `true`: enable pinning with default configuration - Object: set pinning configuration + read_only: Set to `true` to reject document and schema writes, or `false` to allow them. + Writes already in progress may still commit. Metadata updates remain available. + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -1463,7 +1477,11 @@ async def update_metadata( return await self._patch( path_template("/v1/namespaces/{namespace}/metadata", namespace=namespace), body=await async_maybe_transform( - {"pinning": pinning}, namespace_update_metadata_params.NamespaceUpdateMetadataParams + { + "pinning": pinning, + "read_only": read_only, + }, + namespace_update_metadata_params.NamespaceUpdateMetadataParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout diff --git a/src/turbopuffer/types/namespace_metadata.py b/src/turbopuffer/types/namespace_metadata.py index 064d4d40..a1365974 100644 --- a/src/turbopuffer/types/namespace_metadata.py +++ b/src/turbopuffer/types/namespace_metadata.py @@ -94,6 +94,9 @@ class NamespaceMetadata(BaseModel): namespace. """ + read_only: Optional[bool] = None + """Whether document and schema writes are rejected. Omitted when `false`.""" + sharding: Optional[ShardingConfig] = None """ Configuration for namespace sharding, which partitions a namespace's documents diff --git a/src/turbopuffer/types/namespace_update_metadata_params.py b/src/turbopuffer/types/namespace_update_metadata_params.py index 86189fae..8d67724e 100644 --- a/src/turbopuffer/types/namespace_update_metadata_params.py +++ b/src/turbopuffer/types/namespace_update_metadata_params.py @@ -22,5 +22,11 @@ class NamespaceUpdateMetadataParams(TypedDict, total=False): - Object: set pinning configuration """ + read_only: bool + """Set to `true` to reject document and schema writes, or `false` to allow them. + + Writes already in progress may still commit. Metadata updates remain available. + """ + Pinning: TypeAlias = Union[bool, PinningConfigParam] diff --git a/tests/api_resources/test_namespaces.py b/tests/api_resources/test_namespaces.py index 43db21dc..dc5b6c13 100644 --- a/tests/api_resources/test_namespaces.py +++ b/tests/api_resources/test_namespaces.py @@ -494,6 +494,7 @@ def test_method_update_metadata_with_all_params(self, client: Turbopuffer) -> No namespace = client.namespace("namespace").update_metadata( namespace="namespace", pinning=True, + read_only=True, ) assert_matches_type(NamespaceMetadata, namespace, path=["response"]) @@ -1123,6 +1124,7 @@ async def test_method_update_metadata(self, async_client: AsyncTurbopuffer) -> N async def test_method_update_metadata_with_all_params(self, async_client: AsyncTurbopuffer) -> None: namespace = await async_client.namespace("namespace").update_metadata( pinning=True, + read_only=True, ) assert_matches_type(NamespaceMetadata, namespace, path=["response"]) From feb15fd5116201db4a6c9becfaa9ad246babddeb Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:51:55 +0000 Subject: [PATCH 4/4] release: 2.10.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- src/turbopuffer/_version.py | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 427b8ec4..21f60560 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.9.0" + ".": "2.10.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 032b39c4..37218c39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2.10.0 (2026-09-04) + +Full Changelog: [v2.9.0...v2.10.0](https://github.com/turbopuffer/turbopuffer-python/compare/v2.9.0...v2.10.0) + +### Features + +* Add `read_only` namespace field ([691d1ee](https://github.com/turbopuffer/turbopuffer-python/commit/691d1ee1399f85e11cf37eff912f4f0b6fd5e83d)) +* tpuf-engine: expose billed_replicas in pinning metadata ([0fd8f32](https://github.com/turbopuffer/turbopuffer-python/commit/0fd8f323217273d9a22e0f6765621b6dc237ce7b)) + ## 2.9.0 (2026-08-21) Full Changelog: [v2.8.0...v2.9.0](https://github.com/turbopuffer/turbopuffer-python/compare/v2.8.0...v2.9.0) diff --git a/pyproject.toml b/pyproject.toml index dda61704..99b30da2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "turbopuffer" -version = "2.9.0" +version = "2.10.0" description = "The official Python library for the turbopuffer API" dynamic = ["readme"] license = "MIT" diff --git a/src/turbopuffer/_version.py b/src/turbopuffer/_version.py index 1d4f272c..d85b5283 100644 --- a/src/turbopuffer/_version.py +++ b/src/turbopuffer/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "turbopuffer" -__version__ = "2.9.0" # x-release-please-version +__version__ = "2.10.0" # x-release-please-version