Skip to content

Commit 64ca705

Browse files
authored
Add support for list, describe, and delete namespaces in grpc (#517)
## Problem Support for REST sync and async namespaces was added in a previous PR, so this PR adds support for gRPC only. ## Solution Add support for list, describe, and delete namespaces in grpc. ```python from pinecone import Pinecone # Initialize Pinecone client pc = Pinecone(api_key='your-api-key') # Get index index = pc.Index('your-index-name') # Example: List all namespaces (automatic pagination) print("All namespaces:") for namespace in index.list_namespaces(): print(f"{namespace.name}: {namespace.record_count} records") # Example: List namespaces with pagination control results = index.list_namespaces_paginated(limit=10) print(f"First 10 namespaces:") for namespace in results.namespaces: print(f"{namespace.name}: {namespace.record_count} records") # Example: Describe a specific namespace namespace_info = index.describe_namespace(namespace='my-namespace') print(f"Namespace '{namespace_info.name}' has {namespace_info.record_count} records") # Example: Delete a namespace (irreversible!) index.delete_namespace(namespace='namespace-to-delete') ``` ## Type of Change - [ ] Bug fix (non-breaking change which fixes an issue) - [X] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update - [ ] Infrastructure change (CI configs, etc) - [ ] Non-code change (docs, etc) - [ ] None of the above: (explain here) ## Test Plan Added integration and unit tests
1 parent 31af542 commit 64ca705

20 files changed

+517
-86
lines changed

codegen/apis

Submodule apis updated from 7e21ca9 to 827d26f

codegen/buf.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ breaking:
99
deps:
1010
- buf.build/googleapis/googleapis
1111
modules:
12-
- path: apis/_build/2025-01
12+
- path: apis/_build/2025-04

docs/grpc.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,14 @@ Vectors
8080
.. automethod:: pinecone.grpc::GRPCIndex.list
8181

8282
.. automethod:: pinecone.grpc::GRPCIndex.list_paginated
83+
84+
Namespaces
85+
----------
86+
87+
.. automethod:: pinecone.grpc::GRPCIndex.list_namespaces
88+
89+
.. automethod:: pinecone.grpc::GRPCIndex.list_namespaces_paginated
90+
91+
.. automethod:: pinecone.grpc::GRPCIndex.describe_namespace
92+
93+
.. automethod:: pinecone.grpc::GRPCIndex.delete_namespace

pinecone/core/grpc/protos/db_data_2025_01_pb2.py renamed to pinecone/core/grpc/protos/db_data_2025_04_pb2.py

Lines changed: 26 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pinecone/core/grpc/protos/db_data_2025_01_pb2.pyi renamed to pinecone/core/grpc/protos/db_data_2025_04_pb2.pyi

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,42 @@ class NamespaceSummary(_message.Message):
235235
vector_count: int
236236
def __init__(self, vector_count: _Optional[int] = ...) -> None: ...
237237

238+
class ListNamespacesRequest(_message.Message):
239+
__slots__ = ("pagination_token", "limit")
240+
PAGINATION_TOKEN_FIELD_NUMBER: _ClassVar[int]
241+
LIMIT_FIELD_NUMBER: _ClassVar[int]
242+
pagination_token: str
243+
limit: int
244+
def __init__(self, pagination_token: _Optional[str] = ..., limit: _Optional[int] = ...) -> None: ...
245+
246+
class ListNamespacesResponse(_message.Message):
247+
__slots__ = ("namespaces", "pagination")
248+
NAMESPACES_FIELD_NUMBER: _ClassVar[int]
249+
PAGINATION_FIELD_NUMBER: _ClassVar[int]
250+
namespaces: _containers.RepeatedCompositeFieldContainer[NamespaceDescription]
251+
pagination: Pagination
252+
def __init__(self, namespaces: _Optional[_Iterable[_Union[NamespaceDescription, _Mapping]]] = ..., pagination: _Optional[_Union[Pagination, _Mapping]] = ...) -> None: ...
253+
254+
class DescribeNamespaceRequest(_message.Message):
255+
__slots__ = ("namespace",)
256+
NAMESPACE_FIELD_NUMBER: _ClassVar[int]
257+
namespace: str
258+
def __init__(self, namespace: _Optional[str] = ...) -> None: ...
259+
260+
class NamespaceDescription(_message.Message):
261+
__slots__ = ("name", "record_count")
262+
NAME_FIELD_NUMBER: _ClassVar[int]
263+
RECORD_COUNT_FIELD_NUMBER: _ClassVar[int]
264+
name: str
265+
record_count: int
266+
def __init__(self, name: _Optional[str] = ..., record_count: _Optional[int] = ...) -> None: ...
267+
268+
class DeleteNamespaceRequest(_message.Message):
269+
__slots__ = ("namespace",)
270+
NAMESPACE_FIELD_NUMBER: _ClassVar[int]
271+
namespace: str
272+
def __init__(self, namespace: _Optional[str] = ...) -> None: ...
273+
238274
class DescribeIndexStatsResponse(_message.Message):
239275
__slots__ = ("namespaces", "dimension", "index_fullness", "total_vector_count", "metric", "vector_type")
240276
class NamespacesEntry(_message.Message):

pinecone/core/grpc/protos/db_data_2025_01_pb2_grpc.py renamed to pinecone/core/grpc/protos/db_data_2025_04_pb2_grpc.py

Lines changed: 165 additions & 55 deletions
Large diffs are not rendered by default.

pinecone/grpc/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@
5151

5252
from pinecone.db_data.dataclasses import Vector, SparseValues
5353

54-
from pinecone.core.grpc.protos.db_data_2025_01_pb2 import (
54+
from pinecone.core.grpc.protos.db_data_2025_04_pb2 import (
5555
Vector as GRPCVector,
5656
SparseValues as GRPCSparseValues,
5757
DeleteResponse as GRPCDeleteResponse,
5858
)
5959

60+
from pinecone.core.openapi.db_data.models import ListNamespacesResponse
61+
6062
__all__ = [
6163
"GRPCIndex",
6264
"PineconeGRPC",
@@ -67,4 +69,5 @@
6769
"Vector",
6870
"SparseValues",
6971
"PineconeGrpcFuture",
72+
"ListNamespacesResponse",
7073
]

pinecone/grpc/grpc_runner.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pinecone.exceptions.exceptions import PineconeException
1111
from grpc import CallCredentials, Compression
1212
from google.protobuf.message import Message
13+
from pinecone.openapi_support.api_version import API_VERSION
1314

1415

1516
class GrpcRunner:
@@ -21,6 +22,7 @@ def __init__(self, index_name: str, config: Config, grpc_config: GRPCClientConfi
2122
"api-key": config.api_key,
2223
"service-name": index_name,
2324
"client-version": CLIENT_VERSION,
25+
"x-pinecone-api-version": API_VERSION,
2426
}
2527
if self.grpc_client_config.additional_metadata:
2628
self.fixed_metadata.update(self.grpc_client_config.additional_metadata)

pinecone/grpc/index_grpc.py

Lines changed: 147 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from google.protobuf import json_format
55

66
from pinecone.utils.tqdm import tqdm
7+
from pinecone.utils import require_kwargs
78
from concurrent.futures import as_completed, Future
89

910

@@ -15,6 +16,8 @@
1516
parse_upsert_response,
1617
parse_update_response,
1718
parse_delete_response,
19+
parse_namespace_description,
20+
parse_list_namespaces_response,
1821
)
1922
from .vector_factory_grpc import VectorFactoryGRPC
2023
from .sparse_values_factory import SparseValuesFactory
@@ -23,9 +26,11 @@
2326
FetchResponse,
2427
QueryResponse,
2528
IndexDescription as DescribeIndexStatsResponse,
29+
NamespaceDescription,
30+
ListNamespacesResponse,
2631
)
2732
from pinecone.db_control.models.list_response import ListResponse as SimpleListResponse, Pagination
28-
from pinecone.core.grpc.protos.db_data_2025_01_pb2 import (
33+
from pinecone.core.grpc.protos.db_data_2025_04_pb2 import (
2934
Vector as GRPCVector,
3035
QueryVector as GRPCQueryVector,
3136
UpsertRequest,
@@ -39,10 +44,13 @@
3944
DeleteResponse,
4045
UpdateResponse,
4146
SparseValues as GRPCSparseValues,
47+
DescribeNamespaceRequest,
48+
DeleteNamespaceRequest,
49+
ListNamespacesRequest,
4250
)
4351
from pinecone import Vector, SparseValues
4452
from pinecone.db_data.query_results_aggregator import QueryNamespacesResults, QueryResultsAggregator
45-
from pinecone.core.grpc.protos.db_data_2025_01_pb2_grpc import VectorServiceStub
53+
from pinecone.core.grpc.protos.db_data_2025_04_pb2_grpc import VectorServiceStub
4654
from .base import GRPCIndexBase
4755
from .future import PineconeGrpcFuture
4856
from ..db_data.types import (
@@ -54,7 +62,7 @@
5462
)
5563

5664

57-
__all__ = ["GRPCIndex", "GRPCVector", "GRPCQueryVector", "GRPCSparseValues"]
65+
__all__ = ["GRPCIndex", "GRPCVector", "GRPCQueryVector", "GRPCSparseValues", "NamespaceDescription", "ListNamespacesResponse"]
5866

5967
_logger = logging.getLogger(__name__)
6068
""" :meta private: """
@@ -681,6 +689,142 @@ def describe_index_stats(
681689
json_response = json_format.MessageToDict(response)
682690
return parse_stats_response(json_response)
683691

692+
@require_kwargs
693+
def describe_namespace(
694+
self, namespace: str, **kwargs
695+
) -> NamespaceDescription:
696+
"""
697+
The describe_namespace operation returns information about a specific namespace,
698+
including the total number of vectors in the namespace.
699+
700+
Examples:
701+
702+
.. code-block:: python
703+
704+
>>> index.describe_namespace(namespace='my_namespace')
705+
706+
Args:
707+
namespace (str): The namespace to describe.
708+
709+
Returns: NamespaceDescription object which contains information about the namespace.
710+
"""
711+
timeout = kwargs.pop("timeout", None)
712+
request = DescribeNamespaceRequest(namespace=namespace)
713+
response = self.runner.run(self.stub.DescribeNamespace, request, timeout=timeout)
714+
return parse_namespace_description(response)
715+
716+
@require_kwargs
717+
def delete_namespace(
718+
self, namespace: str, **kwargs
719+
) -> Dict[str, Any]:
720+
"""
721+
The delete_namespace operation deletes a namespace from an index.
722+
This operation is irreversible and will permanently delete all data in the namespace.
723+
724+
Examples:
725+
726+
.. code-block:: python
727+
728+
>>> index.delete_namespace(namespace='my_namespace')
729+
730+
Args:
731+
namespace (str): The namespace to delete.
732+
733+
Returns: Empty dictionary indicating successful deletion.
734+
"""
735+
timeout = kwargs.pop("timeout", None)
736+
request = DeleteNamespaceRequest(namespace=namespace)
737+
response = self.runner.run(self.stub.DeleteNamespace, request, timeout=timeout)
738+
return parse_delete_response(response)
739+
740+
@require_kwargs
741+
def list_namespaces_paginated(
742+
self,
743+
limit: Optional[int] = None,
744+
pagination_token: Optional[str] = None,
745+
**kwargs,
746+
) -> ListNamespacesResponse:
747+
"""
748+
The list_namespaces_paginated operation returns a list of all namespaces in a serverless index.
749+
It returns namespaces in a paginated form, with a pagination token to fetch the next page of results.
750+
751+
Examples:
752+
753+
.. code-block:: python
754+
755+
>>> results = index.list_namespaces_paginated(limit=10)
756+
>>> [ns.name for ns in results.namespaces]
757+
['namespace1', 'namespace2', 'namespace3']
758+
>>> results.pagination.next
759+
eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9
760+
>>> next_results = index.list_namespaces_paginated(limit=10, pagination_token=results.pagination.next)
761+
762+
Args:
763+
limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional]
764+
pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned
765+
in the response if additional results are available. [optional]
766+
767+
Returns: ListNamespacesResponse object which contains the list of namespaces and pagination information.
768+
"""
769+
args_dict = self._parse_non_empty_args(
770+
[
771+
("limit", limit),
772+
("pagination_token", pagination_token),
773+
]
774+
)
775+
timeout = kwargs.pop("timeout", None)
776+
request = ListNamespacesRequest(**args_dict, **kwargs)
777+
response = self.runner.run(self.stub.ListNamespaces, request, timeout=timeout)
778+
return parse_list_namespaces_response(response)
779+
780+
@require_kwargs
781+
def list_namespaces(self, limit: Optional[int] = None, **kwargs):
782+
"""
783+
The list_namespaces operation accepts all of the same arguments as list_namespaces_paginated, and returns a generator that yields
784+
each namespace. It automatically handles pagination tokens on your behalf.
785+
786+
Args:
787+
limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional]
788+
789+
Returns:
790+
Returns a generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can
791+
easily iterate over all results. The ``list_namespaces`` method accepts all of the same arguments as list_namespaces_paginated
792+
793+
Examples:
794+
795+
.. code-block:: python
796+
797+
>>> for namespace in index.list_namespaces():
798+
>>> print(namespace.name)
799+
namespace1
800+
namespace2
801+
namespace3
802+
803+
You can convert the generator into a list by wrapping the generator in a call to the built-in ``list`` function:
804+
805+
.. code-block:: python
806+
807+
namespaces = list(index.list_namespaces())
808+
809+
You should be cautious with this approach because it will fetch all namespaces at once, which could be a large number
810+
of network calls and a lot of memory to hold the results.
811+
"""
812+
done = False
813+
while not done:
814+
try:
815+
results = self.list_namespaces_paginated(limit=limit, **kwargs)
816+
except Exception as e:
817+
raise e
818+
819+
if results.namespaces and len(results.namespaces) > 0:
820+
for namespace in results.namespaces:
821+
yield namespace
822+
823+
if results.pagination and results.pagination.next:
824+
kwargs.update({"pagination_token": results.pagination.next})
825+
else:
826+
done = True
827+
684828
@staticmethod
685829
def _parse_non_empty_args(args: List[Tuple[str, Any]]) -> Dict[str, Any]:
686830
return {arg_name: val for arg_name, val in args if val is not None}

pinecone/grpc/sparse_values_factory.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ..db_data import SparseValuesTypeError, SparseValuesMissingKeysError
77
from ..db_data.types import SparseVectorTypedDict
88

9-
from pinecone.core.grpc.protos.db_data_2025_01_pb2 import SparseValues as GRPCSparseValues
9+
from pinecone.core.grpc.protos.db_data_2025_04_pb2 import SparseValues as GRPCSparseValues
1010
from pinecone.core.openapi.db_data.models import SparseValues as OpenApiSparseValues
1111
from pinecone import SparseValues
1212

pinecone/grpc/utils.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
IndexDescription as DescribeIndexStatsResponse,
1414
UpsertResponse,
1515
NamespaceSummary,
16+
NamespaceDescription,
17+
ListNamespacesResponse,
18+
Pagination,
1619
)
1720
from pinecone.db_data.dataclasses import FetchResponse
1821

@@ -126,3 +129,37 @@ def parse_stats_response(response: dict):
126129
total_vector_count=total_vector_count,
127130
_check_type=False,
128131
)
132+
133+
134+
def parse_namespace_description(response: Message) -> NamespaceDescription:
135+
json_response = json_format.MessageToDict(response)
136+
return NamespaceDescription(
137+
name=json_response.get("name", ""),
138+
record_count=json_response.get("recordCount", 0),
139+
_check_type=False,
140+
)
141+
142+
143+
def parse_list_namespaces_response(response: Message) -> ListNamespacesResponse:
144+
json_response = json_format.MessageToDict(response)
145+
146+
namespaces = []
147+
for ns in json_response.get("namespaces", []):
148+
namespaces.append(NamespaceDescription(
149+
name=ns.get("name", ""),
150+
record_count=ns.get("recordCount", 0),
151+
_check_type=False,
152+
))
153+
154+
pagination = None
155+
if "pagination" in json_response and json_response["pagination"]:
156+
pagination = Pagination(
157+
next=json_response["pagination"].get("next", ""),
158+
_check_type=False,
159+
)
160+
161+
return ListNamespacesResponse(
162+
namespaces=namespaces,
163+
pagination=pagination,
164+
_check_type=False,
165+
)

pinecone/grpc/vector_factory_grpc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from ..db_data.types import VectorTuple, VectorTypedDict
1818
from .sparse_values_factory import SparseValuesFactory
1919

20-
from pinecone.core.grpc.protos.db_data_2025_01_pb2 import (
20+
from pinecone.core.grpc.protos.db_data_2025_04_pb2 import (
2121
Vector as GRPCVector,
2222
SparseValues as GRPCSparseValues,
2323
)

tests/integration/data/test_namespace.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import os
21
import time
32
import logging
43

5-
import pytest
6-
74
from pinecone import NamespaceDescription
85

96
logger = logging.getLogger(__name__)
@@ -43,9 +40,6 @@ def delete_all_namespaces(index):
4340
except Exception as e:
4441
logger.error(f"Error in delete_all_namespaces: {e}")
4542

46-
@pytest.mark.skipif(
47-
os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added"
48-
)
4943
class TestNamespaceOperations:
5044
def test_describe_namespace(self, idx):
5145
"""Test describing a namespace"""

tests/integration/data_grpc_futures/stub_backend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import grpc
33
import logging
44
from concurrent import futures
5-
import pinecone.core.grpc.protos.db_data_2025_01_pb2 as pb2
6-
import pinecone.core.grpc.protos.db_data_2025_01_pb2_grpc as pb2_grpc
5+
import pinecone.core.grpc.protos.db_data_2025_04_pb2 as pb2
6+
import pinecone.core.grpc.protos.db_data_2025_04_pb2_grpc as pb2_grpc
77

88
logger = logging.getLogger(__name__)
99

0 commit comments

Comments
 (0)