From ce47fb60ee09ebed543337d1e1d4e41dcd38889a Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 30 May 2025 12:12:59 -0400 Subject: [PATCH 01/15] add describe and delete namespaces method along with tests --- pinecone/db_data/index.py | 36 ++++ pinecone/db_data/index_asyncio.py | 44 +++++ pinecone/db_data/index_asyncio_interface.py | 56 +++++- pinecone/db_data/interfaces.py | 52 +++++- .../resources/asyncio/namespace_asyncio.py | 71 ++++++++ pinecone/db_data/resources/sync/namespace.py | 71 ++++++++ .../sync/namespace_request_factory.py | 29 ++++ tests/integration/data/test_namespace.py | 129 ++++++++++++++ .../data_asyncio/test_namespace_asyncio.py | 163 ++++++++++++++++++ 9 files changed, 649 insertions(+), 2 deletions(-) create mode 100644 pinecone/db_data/resources/asyncio/namespace_asyncio.py create mode 100644 pinecone/db_data/resources/sync/namespace.py create mode 100644 pinecone/db_data/resources/sync/namespace_request_factory.py create mode 100644 tests/integration/data/test_namespace.py create mode 100644 tests/integration/data_asyncio/test_namespace_asyncio.py diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 8878dee21..df94b0ff9 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -8,6 +8,7 @@ from pinecone.openapi_support import ApiClient from pinecone.core.openapi.db_data.api.vector_operations_api import VectorOperationsApi +from pinecone.core.openapi.db_data.api.namespace_operations_api import NamespaceOperationsApi from pinecone.core.openapi.db_data import API_VERSION from pinecone.core.openapi.db_data.models import ( QueryResponse, @@ -15,6 +16,8 @@ UpsertResponse, ListResponse, SearchRecordsResponse, + ListNamespacesResponse, + NamespaceDescription, ) from .dataclasses import Vector, SparseValues, FetchResponse, SearchQuery, SearchRerank from .interfaces import IndexInterface @@ -47,6 +50,7 @@ if TYPE_CHECKING: from pinecone.config import Config, OpenApiConfiguration from .resources.sync.bulk_import import BulkImportResource + from .resources.sync.namespace import NamespaceResource from pinecone.core.openapi.db_data.models import ( StartImportResponse, @@ -75,6 +79,9 @@ class Index(PluginAware, IndexInterface): _bulk_import_resource: Optional["BulkImportResource"] """ @private """ + _namespace_resource: Optional["NamespaceResource"] + """ @private """ + def __init__( self, api_key: str, @@ -115,6 +122,9 @@ def __init__( self._bulk_import_resource = None """ @private """ + self._namespace_resource = None + """ @private """ + # Pass the same api_client to the ImportFeatureMixin super().__init__(api_client=self._api_client) @@ -152,6 +162,15 @@ def bulk_import(self) -> "BulkImportResource": self._bulk_import_resource = BulkImportResource(api_client=self._api_client) return self._bulk_import_resource + @property + def namespace(self) -> "NamespaceResource": + """@private""" + if self._namespace_resource is None: + from .resources.sync.namespace import NamespaceResource + + self._namespace_resource = NamespaceResource(api_client=self._api_client) + return self._namespace_resource + def _openapi_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: return filter_dict(kwargs, OPENAPI_ENDPOINT_PARAMS) @@ -601,3 +620,20 @@ def cancel_import(self, id: str): id (str): The id of the import operation to cancel. """ return self.bulk_import.cancel(id=id) + + @validate_and_convert_errors + def describe_namespace(self, namespace: str) -> "NamespaceDescription": + return self.namespace.describe(namespace=namespace) + + @validate_and_convert_errors + def delete_namespace(self, namespace: str) -> Dict[str, Any]: + return self.namespace.delete(namespace=namespace) + + @validate_and_convert_errors + def list_namespaces(self, + limit: Optional[int] = None, + pagination_token: Optional[str] = None, + ) -> Iterator["NamespaceDescription"]: + return self.namespace.list(limit=limit, pagination_token=pagination_token) + + \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index dee9d4e7f..9240fa548 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -22,6 +22,8 @@ DeleteRequest, ListResponse, SearchRecordsResponse, + ListNamespacesResponse, + NamespaceDescription, ) from ..utils import ( @@ -50,6 +52,7 @@ if TYPE_CHECKING: from .resources.asyncio.bulk_import_asyncio import BulkImportResourceAsyncio + from .resources.asyncio.namespace_asyncio import NamespaceResourceAsyncio from pinecone.core.openapi.db_data.models import ( StartImportResponse, @@ -140,6 +143,9 @@ async def main(): _bulk_import_resource: Optional["BulkImportResourceAsyncio"] """ @private """ + _namespace_resource: Optional["NamespaceResourceAsyncio"] + """ @private """ + def __init__( self, api_key: str, @@ -173,6 +179,9 @@ def __init__( self._bulk_import_resource = None """ @private """ + self._namespace_resource = None + """ @private """ + async def __aenter__(self): return self @@ -241,6 +250,15 @@ def bulk_import(self) -> "BulkImportResourceAsyncio": self._bulk_import_resource = BulkImportResourceAsyncio(api_client=self._api_client) return self._bulk_import_resource + @property + def namespace(self) -> "NamespaceResourceAsyncio": + """@private""" + if self._namespace_resource is None: + from .resources.asyncio.namespace_asyncio import NamespaceResourceAsyncio + + self._namespace_resource = NamespaceResourceAsyncio(api_client=self._api_client) + return self._namespace_resource + @validate_and_convert_errors async def upsert( self, @@ -649,3 +667,29 @@ async def cancel_import(self, id: str): id (str): The id of the import operation to cancel. """ return await self.bulk_import.cancel(id=id) + + @validate_and_convert_errors + async def describe_namespace( + self, + namespace: str, + **kwargs + ) -> "NamespaceDescription": + return await self.namespace.describe(namespace=namespace) + + @validate_and_convert_errors + async def delete_namespace( + self, + namespace: str, + **kwargs + ) -> Dict[str, Any]: + return await self.namespace.delete(namespace=namespace) + + @validate_and_convert_errors + async def list_namespaces( + self, + limit: Optional[int] = None, + pagination_token: Optional[str] = None, + **kwargs + ) -> AsyncIterator["NamespaceDescription"]: + async for namespace in self.namespace.list(limit=limit, pagination_token=pagination_token, **kwargs): + yield namespace \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index a2647d0f8..718d03da1 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, List, Optional, Dict, Any +from typing import Union, List, Optional, Dict, Any, AsyncIterator from pinecone.core.openapi.db_data.models import ( FetchResponse, @@ -10,6 +10,7 @@ ListResponse, SparseValues, SearchRecordsResponse, + NamespaceDescription, ) from .query_results_aggregator import QueryNamespacesResults from .types import ( @@ -805,3 +806,56 @@ async def search_records( ) -> SearchRecordsResponse: """Alias of the search() method.""" pass + + @abstractmethod + async def describe_namespace( + self, + namespace: str, + **kwargs + ) -> NamespaceDescription: + """Describe a namespace within an index, showing the vector count within the namespace. + + Args: + namespace (str): The namespace to describe + **kwargs: Additional arguments to pass to the API call + + Returns: + NamespaceDescription: Information about the namespace including vector count + """ + pass + + @abstractmethod + async def delete_namespace( + self, + namespace: str, + **kwargs + ) -> Dict[str, Any]: + """Delete a namespace from an index. + + Args: + namespace (str): The namespace to delete + **kwargs: Additional arguments to pass to the API call + + Returns: + Dict[str, Any]: Response from the delete operation + """ + pass + + @abstractmethod + async def list_namespaces( + self, + limit: Optional[int] = None, + pagination_token: Optional[str] = None, + **kwargs + ) -> AsyncIterator[NamespaceDescription]: + """Get a list of all namespaces within an index. + + Args: + limit (Optional[int]): Max number of namespaces to return per page + pagination_token (Optional[str]): Token to continue a previous listing operation + **kwargs: Additional arguments to pass to the API call + + Returns: + AsyncIterator[NamespaceDescription]: Async generator yielding namespace descriptions + """ + pass diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index cbcc84b5d..56c5bb57b 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, List, Optional, Dict, Any +from typing import Union, List, Optional, Dict, Any, Iterator from pinecone.core.openapi.db_data.models import ( FetchResponse, @@ -10,6 +10,7 @@ ListResponse, SparseValues, SearchRecordsResponse, + NamespaceDescription, ) from .query_results_aggregator import QueryNamespacesResults from multiprocessing.pool import ApplyResult @@ -679,3 +680,52 @@ def list(self, **kwargs): namespace (Optional[str]): The namespace to fetch vectors from. If not specified, the default namespace is used. [optional] """ pass + + @abstractmethod + def describe_namespace( + self, namespace: str, **kwargs + ) -> NamespaceDescription: + """Describe a namespace within an index, showing the vector count within the namespace. + + Args: + namespace (str): The namespace to describe + **kwargs: Additional arguments to pass to the API call + + Returns: + NamespaceDescription: Information about the namespace including vector count + """ + pass + + @abstractmethod + def delete_namespace( + self, namespace: str, **kwargs + ) -> Dict[str, Any]: + """Delete a namespace from an index. + + Args: + namespace (str): The namespace to delete + **kwargs: Additional arguments to pass to the API call + + Returns: + Dict[str, Any]: Response from the delete operation + """ + pass + + @abstractmethod + def list_namespaces( + self, + limit: Optional[int] = None, + pagination_token: Optional[str] = None, + **kwargs + ) -> Iterator[NamespaceDescription]: + """Get a list of all namespaces within an index. + + Args: + limit (Optional[int]): Max number of namespaces to return per page + pagination_token (Optional[str]): Token to continue a previous listing operation + **kwargs: Additional arguments to pass to the API call + + Returns: + Iterator[NamespaceDescription]: Generator yielding namespace descriptions + """ + pass diff --git a/pinecone/db_data/resources/asyncio/namespace_asyncio.py b/pinecone/db_data/resources/asyncio/namespace_asyncio.py new file mode 100644 index 000000000..6e895dac9 --- /dev/null +++ b/pinecone/db_data/resources/asyncio/namespace_asyncio.py @@ -0,0 +1,71 @@ +from typing import Optional, AsyncIterator, Union + +from pinecone.core.openapi.db_data.api.namespace_operations_api import AsyncioNamespaceOperationsApi +from pinecone.core.openapi.db_data.models import ( + ListNamespacesResponse, + NamespaceDescription, +) + +from pinecone.utils import install_json_repr_override + +from ..sync.namespace_request_factory import NamespaceRequestFactory + +for m in [ListNamespacesResponse, NamespaceDescription]: + install_json_repr_override(m) + + +class NamespaceResourceAsyncio: + def __init__(self, api_client, **kwargs) -> None: + self.__namespace_operations_api = AsyncioNamespaceOperationsApi(api_client) + + async def describe(self, namespace: str) -> NamespaceDescription: + """ + Args: + namespace (str): The namespace to describe + + Returns: + `NamespaceDescription`: Information about the namespace including vector count + + Describe a namespace within an index, showing the vector count within the namespace. + """ + args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace) + return await self.__namespace_operations_api.describe_namespace(**args) + + async def delete(self, namespace: str): + """ + Args: + namespace (str): The namespace to delete + + Delete a namespace from an index. + """ + args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) + return await self.__namespace_operations_api.delete_namespace(**args) + + async def list(self, **kwargs) -> AsyncIterator[NamespaceDescription]: + """ + Args: + limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): When there are multiple pages of results, a pagination token is returned in the response. The token can be used + to fetch the next page of results. [optional] + + Returns: + Returns an async generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can + easily iterate over all results. + + ```python + async for namespace in index.list_namespaces(): + print(namespace) + ``` + """ + done = False + while not done: + args_dict = NamespaceRequestFactory.list_namespaces_args(**kwargs) + results = await self.__namespace_operations_api.list_namespaces_operation(**args_dict) + if len(results.namespaces) > 0: + for namespace in results.namespaces: + yield namespace + + if results.pagination: + kwargs.update({"pagination_token": results.pagination.next}) + else: + done = True \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py new file mode 100644 index 000000000..e148dba43 --- /dev/null +++ b/pinecone/db_data/resources/sync/namespace.py @@ -0,0 +1,71 @@ +from typing import Optional, Iterator, Union + +from pinecone.core.openapi.db_data.api.namespace_operations_api import NamespaceOperationsApi +from pinecone.core.openapi.db_data.models import ( + ListNamespacesResponse, + NamespaceDescription, +) + +from pinecone.utils import install_json_repr_override + +from .namespace_request_factory import NamespaceRequestFactory + +for m in [ListNamespacesResponse, NamespaceDescription]: + install_json_repr_override(m) + + +class NamespaceResource: + def __init__(self, api_client) -> None: + self.__namespace_operations_api = NamespaceOperationsApi(api_client) + + def describe(self, namespace: str) -> NamespaceDescription: + """ + Args: + namespace (str): The namespace to describe + + Returns: + `NamespaceDescription`: Information about the namespace including vector count + + Describe a namespace within an index, showing the vector count within the namespace. + """ + args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace) + return self.__namespace_operations_api.describe_namespace(**args) + + def delete(self, namespace: str): + """ + Args: + namespace (str): The namespace to delete + + Delete a namespace from an index. + """ + args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) + return self.__namespace_operations_api.delete_namespace(**args) + + def list(self, **kwargs) -> Iterator[NamespaceDescription]: + """ + Args: + limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): When there are multiple pages of results, a pagination token is returned in the response. The token can be used + to fetch the next page of results. [optional] + + Returns: + Returns a generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can + easily iterate over all results. + + ```python + for namespace in index.list_namespaces(): + print(namespace) + ``` + """ + done = False + while not done: + args_dict = NamespaceRequestFactory.list_namespaces_args(**kwargs) + results = self.__namespace_operations_api.list_namespaces_operation(**args_dict) + if len(results.namespaces) > 0: + for namespace in results.namespaces: + yield namespace + + if results.pagination: + kwargs.update({"pagination_token": results.pagination.next}) + else: + done = True \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace_request_factory.py b/pinecone/db_data/resources/sync/namespace_request_factory.py new file mode 100644 index 000000000..002f5cae8 --- /dev/null +++ b/pinecone/db_data/resources/sync/namespace_request_factory.py @@ -0,0 +1,29 @@ +from typing import Optional, TypedDict, Any + +from pinecone.utils import parse_non_empty_args + + +class DescribeNamespaceArgs(TypedDict, total=False): + namespace: str + + +class DeleteNamespaceArgs(TypedDict, total=False): + namespace: str + + +class NamespaceRequestFactory: + @staticmethod + def describe_namespace_args(namespace: str) -> DescribeNamespaceArgs: + return {"namespace": namespace} + + @staticmethod + def delete_namespace_args(namespace: str) -> DeleteNamespaceArgs: + if isinstance(namespace, int): + namespace = str(namespace) + return {"namespace": namespace} + + @staticmethod + def list_namespaces_args( + limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs + ) -> dict[str, Any]: + return parse_non_empty_args([("limit", limit), ("pagination_token", pagination_token)]) \ No newline at end of file diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py new file mode 100644 index 000000000..06e300b9d --- /dev/null +++ b/tests/integration/data/test_namespace.py @@ -0,0 +1,129 @@ +import pytest +import time +from typing import List + +from pinecone import Pinecone +from pinecone.core.openapi.db_data.models import NamespaceDescription + + +def setup_namespace_data(index, namespace: str, num_vectors: int = 2): + """Helper function to set up test data in a namespace""" + vectors = [(f"id_{i}", [0.1, 0.2]) for i in range(num_vectors)] + index.upsert(vectors=vectors, namespace=namespace) + # Wait for data to be upserted + time.sleep(5) + + +def verify_namespace_exists(index, namespace: str) -> bool: + """Helper function to verify if a namespace exists""" + try: + index.describe_namespace(namespace) + return True + except Exception: + return False + +# +# def get_namespace_names(index) -> List[str]: +# """Helper function to get all namespace names""" +# return [ns.name for ns in index.list_namespaces()] + + +class TestNamespaceOperations: + def test_describe_namespace(self, idx): + """Test describing a namespace""" + # Setup test data + test_namespace = "test_describe_namespace_sync" + setup_namespace_data(idx, test_namespace) + + # Test describe + description = idx.describe_namespace(test_namespace) + assert isinstance(description, NamespaceDescription) + assert description.name == test_namespace + + def test_delete_namespace(self, idx): + """Test deleting a namespace""" + # Setup test data + test_namespace = "test_delete_namespace_sync" + setup_namespace_data(idx, test_namespace) + + # Verify namespace exists + assert verify_namespace_exists(idx, test_namespace) + + # Delete namespace + idx.delete_namespace(test_namespace) + + # Wait for namespace to be deleted + time.sleep(5) + + # Verify namespace is deleted + assert not verify_namespace_exists(idx, test_namespace) + + # def test_list_namespaces(self, index): + # """Test listing namespaces""" + # # Create multiple test namespaces + # test_namespaces = ["test_list_1", "test_list_2", "test_list_3"] + # for ns in test_namespaces: + # setup_namespace_data(index, ns) + # + # # Get all namespaces + # namespaces = list(index.list_namespaces()) + # + # # Verify results + # assert len(namespaces) >= len(test_namespaces) + # namespace_names = [ns.name for ns in namespaces] + # for test_ns in test_namespaces: + # assert test_ns in namespace_names + # + # # Verify each namespace has correct structure + # for ns in namespaces: + # assert isinstance(ns, NamespaceDescription) + # assert hasattr(ns, 'name') + # assert hasattr(ns, 'vector_count') + + # def test_namespace_operations_with_pagination(self, index): + # """Test namespace operations with pagination""" + # # Create many namespaces to test pagination + # test_namespaces = [f"test_pagination_{i}" for i in range(15)] # More than default page size + # for ns in test_namespaces: + # setup_namespace_data(index, ns) + # + # # Test listing with limit + # namespaces = list(index.list_namespaces(limit=5)) + # assert len(namespaces) >= 5 # Should get at least 5 namespaces + # + # # Test listing all namespaces + # all_namespaces = list(index.list_namespaces()) + # assert len(all_namespaces) >= len(test_namespaces) + # namespace_names = [ns.name for ns in all_namespaces] + # for test_ns in test_namespaces: + # assert test_ns in namespace_names + # + # def test_namespace_operations_with_invalid_namespace(self, index): + # """Test namespace operations with invalid namespace""" + # invalid_namespace = "non_existent_namespace" + # + # # Test describe with invalid namespace + # with pytest.raises(Exception): + # index.describe_namespace(invalid_namespace) + # + # # Test delete with invalid namespace + # with pytest.raises(Exception): + # index.delete_namespace(invalid_namespace) + # + # def test_namespace_operations_with_empty_namespace(self, index): + # """Test namespace operations with empty namespace""" + # empty_namespace = "test_empty_namespace" + # + # # Create empty namespace + # index.upsert(vectors=[], namespace=empty_namespace) + # time.sleep(5) + # + # # Test describe + # description = index.describe_namespace(empty_namespace) + # assert description.name == empty_namespace + # assert description.vector_count == 0 + # + # # Test list includes empty namespace + # namespaces = list(index.list_namespaces()) + # namespace_names = [ns.name for ns in namespaces] + # assert empty_namespace in namespace_names \ No newline at end of file diff --git a/tests/integration/data_asyncio/test_namespace_asyncio.py b/tests/integration/data_asyncio/test_namespace_asyncio.py new file mode 100644 index 000000000..3ad4f8c08 --- /dev/null +++ b/tests/integration/data_asyncio/test_namespace_asyncio.py @@ -0,0 +1,163 @@ +import pytest +import asyncio +from typing import List + +from pinecone.core.openapi.db_data.models import NamespaceDescription +from tests.integration.data_asyncio.conftest import build_asyncioindex_client + + +async def setup_namespace_data(index, namespace: str, num_vectors: int = 3): + """Helper function to set up test data in a namespace""" + vectors = [(f"id_{i}", [0.1, 0.2]) for i in range(num_vectors)] + await index.upsert(vectors=vectors, namespace=namespace) + # Wait for vectors to be upserted + await asyncio.sleep(5) + + +async def verify_namespace_exists(index, namespace: str) -> bool: + """Helper function to verify if a namespace exists""" + try: + await index.describe_namespace(namespace) + return True + except Exception: + return False + + +# async def get_namespace_names(index) -> List[str]: +# """Helper function to get all namespace names""" +# return [ns.name async for ns in index.list_namespaces()] + + +class TestNamespaceOperationsAsyncio: + @pytest.mark.asyncio + async def test_describe_namespace(self, index_host): + """Test describing a namespace""" + asyncio_idx = build_asyncioindex_client(index_host) + + # Setup test data + test_namespace = "test_describe_namespace_async" + await setup_namespace_data(asyncio_idx, test_namespace) + + # Test describe + description = await asyncio_idx.describe_namespace(test_namespace) + assert isinstance(description, NamespaceDescription) + assert description.name == test_namespace + + @pytest.mark.asyncio + async def test_delete_namespace(self, index_host): + """Test deleting a namespace""" + asyncio_idx = build_asyncioindex_client(index_host) + # Setup test data + test_namespace = "test_delete_namespace_async" + await setup_namespace_data(asyncio_idx, test_namespace) + + # Verify namespace exists + assert await verify_namespace_exists(asyncio_idx, test_namespace) + + # Delete namespace + await asyncio_idx.delete_namespace(test_namespace) + + # Wait for namespace to be deleted + await asyncio.sleep(5) + + # Verify namespace is deleted + assert not await verify_namespace_exists(asyncio_idx, test_namespace) + + # @pytest.mark.asyncio + # async def test_list_namespaces(self, index_asyncio): + # """Test listing namespaces""" + # # Create multiple test namespaces + # test_namespaces = ["test_list_1_async", "test_list_2_async", "test_list_3_async"] + # for ns in test_namespaces: + # await setup_namespace_data(index_asyncio, ns) + # + # # Get all namespaces + # namespaces = [ns async for ns in index_asyncio.list_namespaces()] + # + # # Verify results + # assert len(namespaces) >= len(test_namespaces) + # namespace_names = [ns.name for ns in namespaces] + # for test_ns in test_namespaces: + # assert test_ns in namespace_names + # + # # Verify each namespace has correct structure + # for ns in namespaces: + # assert isinstance(ns, NamespaceDescription) + # assert hasattr(ns, 'name') + # assert hasattr(ns, 'vector_count') + # + # @pytest.mark.asyncio + # async def test_namespace_operations_with_pagination(self, index_asyncio): + # """Test namespace operations with pagination""" + # # Create many namespaces to test pagination + # test_namespaces = [f"test_pagination_async_{i}" for i in range(15)] # More than default page size + # for ns in test_namespaces: + # await setup_namespace_data(index_asyncio, ns) + # + # # Test listing with limit + # namespaces = [ns async for ns in index_asyncio.list_namespaces(limit=5)] + # assert len(namespaces) >= 5 # Should get at least 5 namespaces + # + # # Test listing all namespaces + # all_namespaces = [ns async for ns in index_asyncio.list_namespaces()] + # assert len(all_namespaces) >= len(test_namespaces) + # namespace_names = [ns.name for ns in all_namespaces] + # for test_ns in test_namespaces: + # assert test_ns in namespace_names + # + # @pytest.mark.asyncio + # async def test_namespace_operations_with_invalid_namespace(self, index_asyncio): + # """Test namespace operations with invalid namespace""" + # invalid_namespace = "non_existent_namespace_async" + # + # # Test describe with invalid namespace + # with pytest.raises(Exception): + # await index_asyncio.describe_namespace(invalid_namespace) + # + # # Test delete with invalid namespace + # with pytest.raises(Exception): + # await index_asyncio.delete_namespace(invalid_namespace) + + # @pytest.mark.asyncio + # async def test_namespace_operations_with_empty_namespace(self, index_asyncio): + # """Test namespace operations with empty namespace""" + # empty_namespace = "test_empty_namespace_async" + # + # # Create empty namespace + # await index_asyncio.upsert(vectors=[], namespace=empty_namespace) + # await asyncio.sleep(5) + # + # # Test describe + # description = await index_asyncio.describe_namespace(empty_namespace) + # assert description.name == empty_namespace + # assert description.vector_count == 0 + # + # # Test list includes empty namespace + # namespaces = [ns async for ns in index_asyncio.list_namespaces()] + # namespace_names = [ns.name for ns in namespaces] + # assert empty_namespace in namespace_names + # + # @pytest.mark.asyncio + # async def test_concurrent_namespace_operations(self, index_asyncio): + # """Test concurrent namespace operations""" + # # Create multiple namespaces concurrently + # test_namespaces = [f"test_concurrent_{i}" for i in range(5)] + # setup_tasks = [setup_namespace_data(index_asyncio, ns) for ns in test_namespaces] + # await asyncio.gather(*setup_tasks) + # + # # Perform multiple describe operations concurrently + # describe_tasks = [index_asyncio.describe_namespace(ns) for ns in test_namespaces] + # descriptions = await asyncio.gather(*describe_tasks) + # + # # Verify all descriptions + # for ns, desc in zip(test_namespaces, descriptions): + # assert desc.name == ns + # assert desc.vector_count >= 10 + # + # # Delete all namespaces concurrently + # delete_tasks = [index_asyncio.delete_namespace(ns) for ns in test_namespaces] + # await asyncio.gather(*delete_tasks) + # + # # Verify all namespaces are deleted + # for ns in test_namespaces: + # assert not await verify_namespace_exists(index_asyncio, ns) \ No newline at end of file From 3131191c3a231d58cc103584ce6a64be0b8fda7d Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Tue, 3 Jun 2025 11:02:25 -0400 Subject: [PATCH 02/15] finalize list() and extend PluginAware in NamespaceResource --- pinecone/db_data/index.py | 15 +- pinecone/db_data/index_asyncio.py | 28 +-- pinecone/db_data/index_asyncio_interface.py | 54 +++-- pinecone/db_data/interfaces.py | 55 +++-- .../resources/asyncio/namespace_asyncio.py | 49 +++- pinecone/db_data/resources/sync/namespace.py | 52 ++++- .../sync/namespace_request_factory.py | 2 +- tests/integration/data/test_namespace.py | 187 ++++++++------- .../data_asyncio/test_namespace_asyncio.py | 219 +++++++++--------- 9 files changed, 397 insertions(+), 264 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index df94b0ff9..1ae02ffc6 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -1,3 +1,4 @@ +from pinecone.core.openapi.db_data.model.list_namespaces_response import ListNamespacesResponse from pinecone.utils.tqdm import tqdm import warnings import logging @@ -8,7 +9,6 @@ from pinecone.openapi_support import ApiClient from pinecone.core.openapi.db_data.api.vector_operations_api import VectorOperationsApi -from pinecone.core.openapi.db_data.api.namespace_operations_api import NamespaceOperationsApi from pinecone.core.openapi.db_data import API_VERSION from pinecone.core.openapi.db_data.models import ( QueryResponse, @@ -630,10 +630,11 @@ def delete_namespace(self, namespace: str) -> Dict[str, Any]: return self.namespace.delete(namespace=namespace) @validate_and_convert_errors - def list_namespaces(self, - limit: Optional[int] = None, - pagination_token: Optional[str] = None, - ) -> Iterator["NamespaceDescription"]: - return self.namespace.list(limit=limit, pagination_token=pagination_token) + def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: + return self.namespace.list(**kwargs) - \ No newline at end of file + @validate_and_convert_errors + def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + return self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 9240fa548..02f81acd4 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -669,27 +669,19 @@ async def cancel_import(self, id: str): return await self.bulk_import.cancel(id=id) @validate_and_convert_errors - async def describe_namespace( - self, - namespace: str, - **kwargs - ) -> "NamespaceDescription": + async def describe_namespace(self, namespace: str) -> "NamespaceDescription": return await self.namespace.describe(namespace=namespace) @validate_and_convert_errors - async def delete_namespace( - self, - namespace: str, - **kwargs - ) -> Dict[str, Any]: + async def delete_namespace(self, namespace: str) -> Dict[str, Any]: return await self.namespace.delete(namespace=namespace) @validate_and_convert_errors - async def list_namespaces( - self, - limit: Optional[int] = None, - pagination_token: Optional[str] = None, - **kwargs - ) -> AsyncIterator["NamespaceDescription"]: - async for namespace in self.namespace.list(limit=limit, pagination_token=pagination_token, **kwargs): - yield namespace \ No newline at end of file + async def list_namespaces(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: + return await self.namespace.list(**kwargs) + + @validate_and_convert_errors + async def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + return await self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index 718d03da1..03b3c48db 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -11,6 +11,7 @@ SparseValues, SearchRecordsResponse, NamespaceDescription, + ListNamespacesResponse, ) from .query_results_aggregator import QueryNamespacesResults from .types import ( @@ -810,14 +811,12 @@ async def search_records( @abstractmethod async def describe_namespace( self, - namespace: str, - **kwargs + namespace: str ) -> NamespaceDescription: """Describe a namespace within an index, showing the vector count within the namespace. Args: namespace (str): The namespace to describe - **kwargs: Additional arguments to pass to the API call Returns: NamespaceDescription: Information about the namespace including vector count @@ -827,14 +826,12 @@ async def describe_namespace( @abstractmethod async def delete_namespace( self, - namespace: str, - **kwargs + namespace: str ) -> Dict[str, Any]: """Delete a namespace from an index. Args: namespace (str): The namespace to delete - **kwargs: Additional arguments to pass to the API call Returns: Dict[str, Any]: Response from the delete operation @@ -843,19 +840,44 @@ async def delete_namespace( @abstractmethod async def list_namespaces( - self, - limit: Optional[int] = None, - pagination_token: Optional[str] = None, - **kwargs - ) -> AsyncIterator[NamespaceDescription]: - """Get a list of all namespaces within an index. + self, **kwargs + ) -> AsyncIterator[ListNamespacesResponse]: + """List all namespaces in an index. This method automatically handles pagination to return all results. Args: - limit (Optional[int]): Max number of namespaces to return per page - pagination_token (Optional[str]): Token to continue a previous listing operation - **kwargs: Additional arguments to pass to the API call + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] Returns: - AsyncIterator[NamespaceDescription]: Async generator yielding namespace descriptions + `ListNamespacesResponse`: Object containing the list of namespaces. + + Examples: + >>> async for namespace in index.list_namespaces(limit=5): + ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") + Namespace: namespace1, Vector count: 1000 + Namespace: namespace2, Vector count: 2000 """ pass + + @abstractmethod + async def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. + + Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. + + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned + in the response if additional results are available. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + + Examples: + >>> results = await index.list_namespaces_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = await index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) + """ + pass \ No newline at end of file diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index 56c5bb57b..e18610f80 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -11,6 +11,7 @@ SparseValues, SearchRecordsResponse, NamespaceDescription, + ListNamespacesResponse, ) from .query_results_aggregator import QueryNamespacesResults from multiprocessing.pool import ApplyResult @@ -682,9 +683,7 @@ def list(self, **kwargs): pass @abstractmethod - def describe_namespace( - self, namespace: str, **kwargs - ) -> NamespaceDescription: + def describe_namespace(self, namespace: str) -> NamespaceDescription: """Describe a namespace within an index, showing the vector count within the namespace. Args: @@ -697,9 +696,7 @@ def describe_namespace( pass @abstractmethod - def delete_namespace( - self, namespace: str, **kwargs - ) -> Dict[str, Any]: + def delete_namespace(self, namespace: str) -> Dict[str, Any]: """Delete a namespace from an index. Args: @@ -712,20 +709,44 @@ def delete_namespace( pass @abstractmethod - def list_namespaces( - self, - limit: Optional[int] = None, - pagination_token: Optional[str] = None, - **kwargs - ) -> Iterator[NamespaceDescription]: - """Get a list of all namespaces within an index. + def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: + """List all namespaces in an index. This method automatically handles pagination to return all results. Args: - limit (Optional[int]): Max number of namespaces to return per page - pagination_token (Optional[str]): Token to continue a previous listing operation - **kwargs: Additional arguments to pass to the API call + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] Returns: - Iterator[NamespaceDescription]: Generator yielding namespace descriptions + `ListNamespacesResponse`: Object containing the list of namespaces. + + Examples: + >>> results = list(index.list_namespaces(limit=5)) + >>> for namespace in results: + ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") + Namespace: namespace1, Vector count: 1000 + Namespace: namespace2, Vector count: 2000 """ pass + + @abstractmethod + def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. + + Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. + + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned + in the response if additional results are available. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + + Examples: + >>> results = index.list_namespaces_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) + """ + pass \ No newline at end of file diff --git a/pinecone/db_data/resources/asyncio/namespace_asyncio.py b/pinecone/db_data/resources/asyncio/namespace_asyncio.py index 6e895dac9..d26952223 100644 --- a/pinecone/db_data/resources/asyncio/namespace_asyncio.py +++ b/pinecone/db_data/resources/asyncio/namespace_asyncio.py @@ -15,7 +15,7 @@ class NamespaceResourceAsyncio: - def __init__(self, api_client, **kwargs) -> None: + def __init__(self, api_client) -> None: self.__namespace_operations_api = AsyncioNamespaceOperationsApi(api_client) async def describe(self, namespace: str) -> NamespaceDescription: @@ -41,7 +41,7 @@ async def delete(self, namespace: str): args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) return await self.__namespace_operations_api.delete_namespace(**args) - async def list(self, **kwargs) -> AsyncIterator[NamespaceDescription]: + async def list(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: """ Args: limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional] @@ -50,22 +50,55 @@ async def list(self, **kwargs) -> AsyncIterator[NamespaceDescription]: Returns: Returns an async generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can - easily iterate over all results. + easily iterate over all results. The `list` method accepts all of the same arguments as list_paginated ```python async for namespace in index.list_namespaces(): print(namespace) ``` + + You can convert the generator into a list by using an async list comprehension: + + ```python + namespaces = [namespace async for namespace in index.list_namespaces()] + ``` + + You should be cautious with this approach because it will fetch all namespaces at once, which could be a large number + of network calls and a lot of memory to hold the results. """ done = False while not done: - args_dict = NamespaceRequestFactory.list_namespaces_args(**kwargs) - results = await self.__namespace_operations_api.list_namespaces_operation(**args_dict) - if len(results.namespaces) > 0: + results = await self.list_paginated(**kwargs) + if results.namespaces is not None and len(results.namespaces) > 0: for namespace in results.namespaces: yield namespace - if results.pagination: + if results.pagination and results.pagination.next: kwargs.update({"pagination_token": results.pagination.next}) else: - done = True \ No newline at end of file + done = True + + async def list_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + """ + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned + in the response if additional results are available. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + + List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. + + Consider using the `list` method to avoid having to handle pagination tokens manually. + + Examples: + >>> results = await index.list_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = await index.list_paginated(limit=5, pagination_token=results.pagination.next) + """ + args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) + return await self.__namespace_operations_api.list_namespaces_operation(**args) \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index e148dba43..1c861c949 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -6,7 +6,7 @@ NamespaceDescription, ) -from pinecone.utils import install_json_repr_override +from pinecone.utils import install_json_repr_override, PluginAware from .namespace_request_factory import NamespaceRequestFactory @@ -14,9 +14,10 @@ install_json_repr_override(m) -class NamespaceResource: +class NamespaceResource(PluginAware): def __init__(self, api_client) -> None: self.__namespace_operations_api = NamespaceOperationsApi(api_client) + super().__init__(api_client) def describe(self, namespace: str) -> NamespaceDescription: """ @@ -41,7 +42,7 @@ def delete(self, namespace: str): args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) return self.__namespace_operations_api.delete_namespace(**args) - def list(self, **kwargs) -> Iterator[NamespaceDescription]: + def list(self, **kwargs) -> Iterator[ListNamespacesResponse]: """ Args: limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional] @@ -50,22 +51,55 @@ def list(self, **kwargs) -> Iterator[NamespaceDescription]: Returns: Returns a generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can - easily iterate over all results. + easily iterate over all results. The `list` method accepts all of the same arguments as list_paginated ```python for namespace in index.list_namespaces(): print(namespace) ``` + + You can convert the generator into a list by wrapping the generator in a call to the built-in `list` function: + + ```python + namespaces = list(index.list_namespaces()) + ``` + + You should be cautious with this approach because it will fetch all namespaces at once, which could be a large number + of network calls and a lot of memory to hold the results. """ done = False while not done: - args_dict = NamespaceRequestFactory.list_namespaces_args(**kwargs) - results = self.__namespace_operations_api.list_namespaces_operation(**args_dict) - if len(results.namespaces) > 0: + results = self.list_paginated(**kwargs) + if results.namespaces is not None and len(results.namespaces) > 0: for namespace in results.namespaces: yield namespace - if results.pagination: + if results.pagination and results.pagination.next: kwargs.update({"pagination_token": results.pagination.next}) else: - done = True \ No newline at end of file + done = True + + def list_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + """ + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned + in the response if additional results are available. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + + List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. + + Consider using the `list` method to avoid having to handle pagination tokens manually. + + Examples: + >>> results = index.list_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = index.list_paginated(limit=5, pagination_token=results.pagination.next) + """ + args_dict = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) + return self.__namespace_operations_api.list_namespaces_operation(**args_dict) \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace_request_factory.py b/pinecone/db_data/resources/sync/namespace_request_factory.py index 002f5cae8..c6b639001 100644 --- a/pinecone/db_data/resources/sync/namespace_request_factory.py +++ b/pinecone/db_data/resources/sync/namespace_request_factory.py @@ -24,6 +24,6 @@ def delete_namespace_args(namespace: str) -> DeleteNamespaceArgs: @staticmethod def list_namespaces_args( - limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs + limit: Optional[int] = None, pagination_token: Optional[str] = None ) -> dict[str, Any]: return parse_non_empty_args([("limit", limit), ("pagination_token", pagination_token)]) \ No newline at end of file diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 06e300b9d..5147b04a8 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -1,6 +1,4 @@ -import pytest import time -from typing import List from pinecone import Pinecone from pinecone.core.openapi.db_data.models import NamespaceDescription @@ -22,10 +20,24 @@ def verify_namespace_exists(index, namespace: str) -> bool: except Exception: return False -# -# def get_namespace_names(index) -> List[str]: -# """Helper function to get all namespace names""" -# return [ns.name for ns in index.list_namespaces()] + +def delete_all_namespaces(index): + """Helper function to delete all namespaces in an index""" + try: + # Get all namespaces + namespaces = list(index.list_namespaces()) + + # Delete each namespace + for namespace in namespaces: + try: + index.delete_namespace(namespace.name) + except Exception as e: + print(f"Error deleting namespace {namespace.name}: {e}") + + # Wait for deletions to complete + time.sleep(5) + except Exception as e: + print(f"Error in delete_all_namespaces: {e}") class TestNamespaceOperations: @@ -35,10 +47,14 @@ def test_describe_namespace(self, idx): test_namespace = "test_describe_namespace_sync" setup_namespace_data(idx, test_namespace) - # Test describe - description = idx.describe_namespace(test_namespace) - assert isinstance(description, NamespaceDescription) - assert description.name == test_namespace + try: + # Test describe + description = idx.describe_namespace(test_namespace) + assert isinstance(description, NamespaceDescription) + assert description.name == test_namespace + finally: + # Delete all namespaces before next test is run + delete_all_namespaces(idx) def test_delete_namespace(self, idx): """Test deleting a namespace""" @@ -53,77 +69,88 @@ def test_delete_namespace(self, idx): idx.delete_namespace(test_namespace) # Wait for namespace to be deleted - time.sleep(5) + time.sleep(10) # Verify namespace is deleted assert not verify_namespace_exists(idx, test_namespace) - # def test_list_namespaces(self, index): - # """Test listing namespaces""" - # # Create multiple test namespaces - # test_namespaces = ["test_list_1", "test_list_2", "test_list_3"] - # for ns in test_namespaces: - # setup_namespace_data(index, ns) - # - # # Get all namespaces - # namespaces = list(index.list_namespaces()) - # - # # Verify results - # assert len(namespaces) >= len(test_namespaces) - # namespace_names = [ns.name for ns in namespaces] - # for test_ns in test_namespaces: - # assert test_ns in namespace_names - # - # # Verify each namespace has correct structure - # for ns in namespaces: - # assert isinstance(ns, NamespaceDescription) - # assert hasattr(ns, 'name') - # assert hasattr(ns, 'vector_count') - - # def test_namespace_operations_with_pagination(self, index): - # """Test namespace operations with pagination""" - # # Create many namespaces to test pagination - # test_namespaces = [f"test_pagination_{i}" for i in range(15)] # More than default page size - # for ns in test_namespaces: - # setup_namespace_data(index, ns) - # - # # Test listing with limit - # namespaces = list(index.list_namespaces(limit=5)) - # assert len(namespaces) >= 5 # Should get at least 5 namespaces - # - # # Test listing all namespaces - # all_namespaces = list(index.list_namespaces()) - # assert len(all_namespaces) >= len(test_namespaces) - # namespace_names = [ns.name for ns in all_namespaces] - # for test_ns in test_namespaces: - # assert test_ns in namespace_names - # - # def test_namespace_operations_with_invalid_namespace(self, index): - # """Test namespace operations with invalid namespace""" - # invalid_namespace = "non_existent_namespace" - # - # # Test describe with invalid namespace - # with pytest.raises(Exception): - # index.describe_namespace(invalid_namespace) - # - # # Test delete with invalid namespace - # with pytest.raises(Exception): - # index.delete_namespace(invalid_namespace) - # - # def test_namespace_operations_with_empty_namespace(self, index): - # """Test namespace operations with empty namespace""" - # empty_namespace = "test_empty_namespace" - # - # # Create empty namespace - # index.upsert(vectors=[], namespace=empty_namespace) - # time.sleep(5) - # - # # Test describe - # description = index.describe_namespace(empty_namespace) - # assert description.name == empty_namespace - # assert description.vector_count == 0 - # - # # Test list includes empty namespace - # namespaces = list(index.list_namespaces()) - # namespace_names = [ns.name for ns in namespaces] - # assert empty_namespace in namespace_names \ No newline at end of file + def test_list_namespaces(self, idx): + """Test listing namespaces""" + # Create multiple test namespaces + test_namespaces = ["test_list_1", "test_list_2", "test_list_3"] + for ns in test_namespaces: + setup_namespace_data(idx, ns) + + try: + # Get all namespaces + namespaces = list(idx.list_namespaces()) + + # Verify results + assert len(namespaces) == len(test_namespaces) + namespace_names = [ns.name for ns in namespaces] + for test_ns in test_namespaces: + assert test_ns in namespace_names + + # Verify each namespace has correct structure + for ns in namespaces: + assert isinstance(ns, NamespaceDescription) + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') + finally: + # Delete all namespaces before next test is run + delete_all_namespaces(idx) + + def test_list_namespaces_with_limit(self, idx): + """Test listing namespaces with limit""" + # Create multiple test namespaces + test_namespaces = [f"test_limit_{i}" for i in range(5)] + for ns in test_namespaces: + setup_namespace_data(idx, ns) + + try: + # Get namespaces with limit + namespaces = list(idx.list_namespaces(limit=2)) + + # Verify results + assert len(namespaces) >= 2 # Should get at least 2 namespaces + for ns in namespaces: + assert isinstance(ns, NamespaceDescription) + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') + + finally: + # Delete all namespaces before next test is run + delete_all_namespaces(idx) + + + def test_list_namespaces_paginated(self, idx): + """Test listing namespaces with pagination""" + # Create multiple test namespaces + test_namespaces = [f"test_paginated_{i}" for i in range(5)] + for ns in test_namespaces: + setup_namespace_data(idx, ns) + + try: + # Get first page + response = idx.list_namespaces_paginated(limit=2) + assert len(response.namespaces) == 2 + assert response.pagination.next is not None + + # Get second page + next_response = idx.list_namespaces_paginated( + limit=2, + pagination_token=response.pagination.next + ) + assert len(next_response.namespaces) == 2 + assert next_response.pagination.next is not None + + # Get final page + final_response = idx.list_namespaces_paginated( + limit=2, + pagination_token=next_response.pagination.next + ) + print(final_response) + assert len(final_response.namespaces) == 1 + assert final_response.pagination is None + finally: + delete_all_namespaces(idx) \ No newline at end of file diff --git a/tests/integration/data_asyncio/test_namespace_asyncio.py b/tests/integration/data_asyncio/test_namespace_asyncio.py index 3ad4f8c08..9efcbad03 100644 --- a/tests/integration/data_asyncio/test_namespace_asyncio.py +++ b/tests/integration/data_asyncio/test_namespace_asyncio.py @@ -1,12 +1,11 @@ import pytest import asyncio -from typing import List from pinecone.core.openapi.db_data.models import NamespaceDescription from tests.integration.data_asyncio.conftest import build_asyncioindex_client -async def setup_namespace_data(index, namespace: str, num_vectors: int = 3): +async def setup_namespace_data(index, namespace: str, num_vectors: int = 2): """Helper function to set up test data in a namespace""" vectors = [(f"id_{i}", [0.1, 0.2]) for i in range(num_vectors)] await index.upsert(vectors=vectors, namespace=namespace) @@ -23,9 +22,23 @@ async def verify_namespace_exists(index, namespace: str) -> bool: return False -# async def get_namespace_names(index) -> List[str]: -# """Helper function to get all namespace names""" -# return [ns.name async for ns in index.list_namespaces()] +async def delete_all_namespaces(index): + """Helper function to delete all namespaces in an index""" + try: + # Get all namespaces + namespaces = await index.list_namespaces_paginated() + + # Delete each namespace + for namespace in namespaces.namespaces: + try: + await index.delete_namespace(namespace.name) + except Exception as e: + print(f"Error deleting namespace {namespace.name}: {e}") + + # Wait for deletions to complete + await asyncio.sleep(5) + except Exception as e: + print(f"Error in delete_all_namespaces: {e}") class TestNamespaceOperationsAsyncio: @@ -38,10 +51,14 @@ async def test_describe_namespace(self, index_host): test_namespace = "test_describe_namespace_async" await setup_namespace_data(asyncio_idx, test_namespace) - # Test describe - description = await asyncio_idx.describe_namespace(test_namespace) - assert isinstance(description, NamespaceDescription) - assert description.name == test_namespace + try: + # Test describe + description = await asyncio_idx.describe_namespace(test_namespace) + assert isinstance(description, NamespaceDescription) + assert description.name == test_namespace + finally: + # Delete all namespaces before next test is run + await delete_all_namespaces(asyncio_idx) @pytest.mark.asyncio async def test_delete_namespace(self, index_host): @@ -58,106 +75,92 @@ async def test_delete_namespace(self, index_host): await asyncio_idx.delete_namespace(test_namespace) # Wait for namespace to be deleted - await asyncio.sleep(5) + await asyncio.sleep(10) # Verify namespace is deleted assert not await verify_namespace_exists(asyncio_idx, test_namespace) - # @pytest.mark.asyncio - # async def test_list_namespaces(self, index_asyncio): - # """Test listing namespaces""" - # # Create multiple test namespaces - # test_namespaces = ["test_list_1_async", "test_list_2_async", "test_list_3_async"] - # for ns in test_namespaces: - # await setup_namespace_data(index_asyncio, ns) - # - # # Get all namespaces - # namespaces = [ns async for ns in index_asyncio.list_namespaces()] - # - # # Verify results - # assert len(namespaces) >= len(test_namespaces) - # namespace_names = [ns.name for ns in namespaces] - # for test_ns in test_namespaces: - # assert test_ns in namespace_names - # - # # Verify each namespace has correct structure - # for ns in namespaces: - # assert isinstance(ns, NamespaceDescription) - # assert hasattr(ns, 'name') - # assert hasattr(ns, 'vector_count') - # - # @pytest.mark.asyncio - # async def test_namespace_operations_with_pagination(self, index_asyncio): - # """Test namespace operations with pagination""" - # # Create many namespaces to test pagination - # test_namespaces = [f"test_pagination_async_{i}" for i in range(15)] # More than default page size - # for ns in test_namespaces: - # await setup_namespace_data(index_asyncio, ns) - # - # # Test listing with limit - # namespaces = [ns async for ns in index_asyncio.list_namespaces(limit=5)] - # assert len(namespaces) >= 5 # Should get at least 5 namespaces - # - # # Test listing all namespaces - # all_namespaces = [ns async for ns in index_asyncio.list_namespaces()] - # assert len(all_namespaces) >= len(test_namespaces) - # namespace_names = [ns.name for ns in all_namespaces] - # for test_ns in test_namespaces: - # assert test_ns in namespace_names - # - # @pytest.mark.asyncio - # async def test_namespace_operations_with_invalid_namespace(self, index_asyncio): - # """Test namespace operations with invalid namespace""" - # invalid_namespace = "non_existent_namespace_async" - # - # # Test describe with invalid namespace - # with pytest.raises(Exception): - # await index_asyncio.describe_namespace(invalid_namespace) - # - # # Test delete with invalid namespace - # with pytest.raises(Exception): - # await index_asyncio.delete_namespace(invalid_namespace) - - # @pytest.mark.asyncio - # async def test_namespace_operations_with_empty_namespace(self, index_asyncio): - # """Test namespace operations with empty namespace""" - # empty_namespace = "test_empty_namespace_async" - # - # # Create empty namespace - # await index_asyncio.upsert(vectors=[], namespace=empty_namespace) - # await asyncio.sleep(5) - # - # # Test describe - # description = await index_asyncio.describe_namespace(empty_namespace) - # assert description.name == empty_namespace - # assert description.vector_count == 0 - # - # # Test list includes empty namespace - # namespaces = [ns async for ns in index_asyncio.list_namespaces()] - # namespace_names = [ns.name for ns in namespaces] - # assert empty_namespace in namespace_names - # - # @pytest.mark.asyncio - # async def test_concurrent_namespace_operations(self, index_asyncio): - # """Test concurrent namespace operations""" - # # Create multiple namespaces concurrently - # test_namespaces = [f"test_concurrent_{i}" for i in range(5)] - # setup_tasks = [setup_namespace_data(index_asyncio, ns) for ns in test_namespaces] - # await asyncio.gather(*setup_tasks) - # - # # Perform multiple describe operations concurrently - # describe_tasks = [index_asyncio.describe_namespace(ns) for ns in test_namespaces] - # descriptions = await asyncio.gather(*describe_tasks) - # - # # Verify all descriptions - # for ns, desc in zip(test_namespaces, descriptions): - # assert desc.name == ns - # assert desc.vector_count >= 10 - # - # # Delete all namespaces concurrently - # delete_tasks = [index_asyncio.delete_namespace(ns) for ns in test_namespaces] - # await asyncio.gather(*delete_tasks) - # - # # Verify all namespaces are deleted - # for ns in test_namespaces: - # assert not await verify_namespace_exists(index_asyncio, ns) \ No newline at end of file + @pytest.mark.asyncio + async def test_list_namespaces(self, index_host): + """Test listing namespaces""" + asyncio_idx = build_asyncioindex_client(index_host) + # Create multiple test namespaces + test_namespaces = ["test_list_1_async", "test_list_2_async", "test_list_3_async"] + for ns in test_namespaces: + await setup_namespace_data(asyncio_idx, ns) + + try: + # Get all namespaces + namespaces = await asyncio_idx.list_namespaces_paginated() + + # Verify results + assert len(namespaces.namespaces) >= len(test_namespaces) + namespace_names = [ns.name for ns in namespaces.namespaces] + for test_ns in test_namespaces: + assert test_ns in namespace_names + + # Verify each namespace has correct structure + for ns in namespaces.namespaces: + assert isinstance(ns, NamespaceDescription) + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') + finally: + # Delete all namespaces before next test is run + await delete_all_namespaces(asyncio_idx) + + @pytest.mark.asyncio + async def test_list_namespaces_with_limit(self, index_host): + """Test listing namespaces with limit""" + asyncio_idx = build_asyncioindex_client(index_host) + # Create multiple test namespaces + test_namespaces = [f"test_limit_async_{i}" for i in range(5)] + for ns in test_namespaces: + await setup_namespace_data(asyncio_idx, ns) + + try: + # Get namespaces with limit + namespaces = await asyncio_idx.list_namespaces_paginated(limit=2) + + # Verify results + assert len(namespaces.namespaces) == 2 # Should get exactly 2 namespaces + for ns in namespaces.namespaces: + assert isinstance(ns, NamespaceDescription) + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') + finally: + # Delete all namespaces before next test is run + await delete_all_namespaces(asyncio_idx) + + @pytest.mark.asyncio + async def test_list_namespaces_paginated(self, index_host): + """Test listing namespaces with pagination""" + asyncio_idx = build_asyncioindex_client(index_host) + # Create multiple test namespaces + test_namespaces = [f"test_paginated_async_{i}" for i in range(5)] + for ns in test_namespaces: + await setup_namespace_data(asyncio_idx, ns) + + try: + # Get first page + response = await asyncio_idx.list_namespaces_paginated(limit=2) + assert len(response.namespaces) == 2 + assert response.pagination.next is not None + + # Get second page + next_response = await asyncio_idx.list_namespaces_paginated( + limit=2, + pagination_token=response.pagination.next + ) + assert len(next_response.namespaces) == 2 + assert next_response.pagination.next is not None + + # Get final page + final_response = await asyncio_idx.list_namespaces_paginated( + limit=2, + pagination_token=next_response.pagination.next + ) + assert len(final_response.namespaces) == 1 + assert final_response.pagination is None + finally: + # Delete all namespaces before next test is run + await delete_all_namespaces(asyncio_idx) \ No newline at end of file From c510de9b9a71f98f18e16003feaa3f356c492cb7 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Tue, 3 Jun 2025 11:32:12 -0400 Subject: [PATCH 03/15] remove plugin aware --- pinecone/db_data/index.py | 1 - pinecone/db_data/index_asyncio.py | 3 ++- pinecone/db_data/resources/asyncio/namespace_asyncio.py | 2 +- pinecone/db_data/resources/sync/namespace.py | 7 +++---- tests/integration/data/test_namespace.py | 1 - 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 1ae02ffc6..9b7ce6091 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -1,4 +1,3 @@ -from pinecone.core.openapi.db_data.model.list_namespaces_response import ListNamespacesResponse from pinecone.utils.tqdm import tqdm import warnings import logging diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 02f81acd4..e619668a7 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -678,7 +678,8 @@ async def delete_namespace(self, namespace: str) -> Dict[str, Any]: @validate_and_convert_errors async def list_namespaces(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: - return await self.namespace.list(**kwargs) + async for namespace in self.namespace.list(**kwargs): + yield namespace @validate_and_convert_errors async def list_namespaces_paginated( diff --git a/pinecone/db_data/resources/asyncio/namespace_asyncio.py b/pinecone/db_data/resources/asyncio/namespace_asyncio.py index d26952223..5a6af282f 100644 --- a/pinecone/db_data/resources/asyncio/namespace_asyncio.py +++ b/pinecone/db_data/resources/asyncio/namespace_asyncio.py @@ -1,4 +1,4 @@ -from typing import Optional, AsyncIterator, Union +from typing import Optional, AsyncIterator from pinecone.core.openapi.db_data.api.namespace_operations_api import AsyncioNamespaceOperationsApi from pinecone.core.openapi.db_data.models import ( diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index 1c861c949..3ead03648 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -1,4 +1,4 @@ -from typing import Optional, Iterator, Union +from typing import Optional, Iterator from pinecone.core.openapi.db_data.api.namespace_operations_api import NamespaceOperationsApi from pinecone.core.openapi.db_data.models import ( @@ -6,7 +6,7 @@ NamespaceDescription, ) -from pinecone.utils import install_json_repr_override, PluginAware +from pinecone.utils import install_json_repr_override from .namespace_request_factory import NamespaceRequestFactory @@ -14,10 +14,9 @@ install_json_repr_override(m) -class NamespaceResource(PluginAware): +class NamespaceResource(): def __init__(self, api_client) -> None: self.__namespace_operations_api = NamespaceOperationsApi(api_client) - super().__init__(api_client) def describe(self, namespace: str) -> NamespaceDescription: """ diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 5147b04a8..923000288 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -1,6 +1,5 @@ import time -from pinecone import Pinecone from pinecone.core.openapi.db_data.models import NamespaceDescription From 554ba4a3dcbb64881b4ee4f37c8212b16a5c2997 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Tue, 3 Jun 2025 15:28:09 -0400 Subject: [PATCH 04/15] add plugin-aware to namespace resource --- pinecone/db_data/index.py | 7 ++++++- pinecone/db_data/resources/sync/namespace.py | 22 +++++++++++++++++--- tests/integration/data/test_namespace.py | 1 - 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 9b7ce6091..3c8d1ad82 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -167,7 +167,12 @@ def namespace(self) -> "NamespaceResource": if self._namespace_resource is None: from .resources.sync.namespace import NamespaceResource - self._namespace_resource = NamespaceResource(api_client=self._api_client) + self._namespace_resource = NamespaceResource( + api_client=self._api_client, + config=self._config, + openapi_config=self._openapi_config, + pool_threads=self._pool_threads, + ) return self._namespace_resource def _openapi_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index 3ead03648..14cbad989 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -6,7 +6,7 @@ NamespaceDescription, ) -from pinecone.utils import install_json_repr_override +from pinecone.utils import install_json_repr_override, PluginAware from .namespace_request_factory import NamespaceRequestFactory @@ -14,9 +14,25 @@ install_json_repr_override(m) -class NamespaceResource(): - def __init__(self, api_client) -> None: +class NamespaceResource(PluginAware): + def __init__( + self, + api_client, + config, + openapi_config, + pool_threads: int, + ) -> None: + self.config = config + """ @private """ + + self._openapi_config = openapi_config + """ @private """ + + self._pool_threads = pool_threads + """ @private """ + self.__namespace_operations_api = NamespaceOperationsApi(api_client) + super().__init__() def describe(self, namespace: str) -> NamespaceDescription: """ diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 923000288..983e06dbd 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -148,7 +148,6 @@ def test_list_namespaces_paginated(self, idx): limit=2, pagination_token=next_response.pagination.next ) - print(final_response) assert len(final_response.namespaces) == 1 assert final_response.pagination is None finally: From dec94b224a256bcd1663ee2774ec91ca0bad0694 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 6 Jun 2025 13:13:09 -0400 Subject: [PATCH 05/15] update integration tests for index name and disable grpc tests --- .github/workflows/testing-integration.yaml | 2 +- tests/integration/helpers/helpers.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/testing-integration.yaml b/.github/workflows/testing-integration.yaml index 189f5dd98..c360de8f5 100644 --- a/.github/workflows/testing-integration.yaml +++ b/.github/workflows/testing-integration.yaml @@ -81,7 +81,7 @@ jobs: matrix: python_version: ${{ fromJson(inputs.python_versions_json) }} test_suite: - - data +# - data # commenting until grpc namespaces endpoints are added steps: - uses: actions/checkout@v4 - name: Setup Poetry diff --git a/tests/integration/helpers/helpers.py b/tests/integration/helpers/helpers.py index 8cb069dd7..dc648fd44 100644 --- a/tests/integration/helpers/helpers.py +++ b/tests/integration/helpers/helpers.py @@ -280,11 +280,7 @@ async def asyncio_wait_until( def default_create_index_params(request, run_id): - github_actor = os.getenv("GITHUB_ACTOR", None) - user = os.getenv("USER", None) - index_owner = github_actor or user or "unknown" - - index_name = f"{index_owner}-{str(uuid.uuid4())}" + index_name = f"{str(uuid.uuid4())}" tags = index_tags(request, run_id) cloud = get_environment_var("SERVERLESS_CLOUD", "aws") region = get_environment_var("SERVERLESS_REGION", "us-west-2") From 5a67dbe1768c974f1c100d798136546e5568c1fd Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 6 Jun 2025 13:21:06 -0400 Subject: [PATCH 06/15] disable grpc tests for now --- .github/workflows/testing-integration.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing-integration.yaml b/.github/workflows/testing-integration.yaml index c360de8f5..7a97204c3 100644 --- a/.github/workflows/testing-integration.yaml +++ b/.github/workflows/testing-integration.yaml @@ -76,12 +76,13 @@ jobs: grpc-sync: name: grpc sync ${{ matrix.python_version }} ${{ matrix.test_suite }} runs-on: ubuntu-latest + if: false # This will prevent the job from running strategy: fail-fast: false matrix: python_version: ${{ fromJson(inputs.python_versions_json) }} test_suite: -# - data # commenting until grpc namespaces endpoints are added + - data steps: - uses: actions/checkout@v4 - name: Setup Poetry From 0cd85a5dbe0cf430b2de81d668edd1e533d9dfbd Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 6 Jun 2025 13:28:30 -0400 Subject: [PATCH 07/15] update comment --- .github/workflows/testing-integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing-integration.yaml b/.github/workflows/testing-integration.yaml index 7a97204c3..ec03dfba6 100644 --- a/.github/workflows/testing-integration.yaml +++ b/.github/workflows/testing-integration.yaml @@ -76,7 +76,7 @@ jobs: grpc-sync: name: grpc sync ${{ matrix.python_version }} ${{ matrix.test_suite }} runs-on: ubuntu-latest - if: false # This will prevent the job from running + if: false # TODO: Uncomment this when grpc namespaces is ready strategy: fail-fast: false matrix: From 2b4c0e4fa40c227b8d85b4671b5ad711df75d501 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 6 Jun 2025 13:45:02 -0400 Subject: [PATCH 08/15] enable grpc tests and disable grpc-namespaces test --- .github/workflows/testing-integration.yaml | 1 - tests/integration/data/test_namespace.py | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/testing-integration.yaml b/.github/workflows/testing-integration.yaml index ec03dfba6..189f5dd98 100644 --- a/.github/workflows/testing-integration.yaml +++ b/.github/workflows/testing-integration.yaml @@ -76,7 +76,6 @@ jobs: grpc-sync: name: grpc sync ${{ matrix.python_version }} ${{ matrix.test_suite }} runs-on: ubuntu-latest - if: false # TODO: Uncomment this when grpc namespaces is ready strategy: fail-fast: false matrix: diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 983e06dbd..41ff3e815 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -1,5 +1,8 @@ +import os import time +import pytest + from pinecone.core.openapi.db_data.models import NamespaceDescription @@ -40,6 +43,9 @@ def delete_all_namespaces(index): class TestNamespaceOperations: + @pytest.mark.skipif( + os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added" + ) def test_describe_namespace(self, idx): """Test describing a namespace""" # Setup test data From 2c81e6c61ac5fb00b89caf3bd8141e3171854a67 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 6 Jun 2025 14:18:09 -0400 Subject: [PATCH 09/15] final cleanup --- pinecone/db_data/index.py | 6 +++--- pinecone/db_data/index_asyncio.py | 2 +- pinecone/db_data/resources/sync/namespace.py | 10 +++++----- tests/integration/data/test_namespace.py | 7 +++---- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 307e40843..9256eb5ef 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -79,7 +79,7 @@ class Index(PluginAware, IndexInterface): """ :meta private: """ _namespace_resource: Optional["NamespaceResource"] - """ @private """ + """ :meta private: """ def __init__( self, @@ -122,7 +122,7 @@ def __init__( """ :meta private: """ self._namespace_resource = None - """ @private """ + """ :meta private: """ # Pass the same api_client to the ImportFeatureMixin super().__init__(api_client=self._api_client) @@ -163,7 +163,7 @@ def bulk_import(self) -> "BulkImportResource": @property def namespace(self) -> "NamespaceResource": - """@private""" + """:meta private:""" if self._namespace_resource is None: from .resources.sync.namespace import NamespaceResource diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 4ab88725f..0c8fe8e48 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -252,7 +252,7 @@ def bulk_import(self) -> "BulkImportResourceAsyncio": @property def namespace(self) -> "NamespaceResourceAsyncio": - """@private""" + """:meta private:""" if self._namespace_resource is None: from .resources.asyncio.namespace_asyncio import NamespaceResourceAsyncio diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index 14cbad989..aabf6da12 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -23,13 +23,13 @@ def __init__( pool_threads: int, ) -> None: self.config = config - """ @private """ + """ :meta private: """ self._openapi_config = openapi_config - """ @private """ + """ :meta private: """ self._pool_threads = pool_threads - """ @private """ + """ :meta private: """ self.__namespace_operations_api = NamespaceOperationsApi(api_client) super().__init__() @@ -116,5 +116,5 @@ def list_paginated( eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 >>> next_results = index.list_paginated(limit=5, pagination_token=results.pagination.next) """ - args_dict = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) - return self.__namespace_operations_api.list_namespaces_operation(**args_dict) \ No newline at end of file + args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) + return self.__namespace_operations_api.list_namespaces_operation(**args) \ No newline at end of file diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 41ff3e815..20b757284 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -41,11 +41,10 @@ def delete_all_namespaces(index): except Exception as e: print(f"Error in delete_all_namespaces: {e}") - +@pytest.mark.skipif( + os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added" +) class TestNamespaceOperations: - @pytest.mark.skipif( - os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added" - ) def test_describe_namespace(self, idx): """Test describing a namespace""" # Setup test data From 6a011e863bf81e2498f0b6ee17e66ad21d968cd3 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Mon, 9 Jun 2025 10:22:36 -0400 Subject: [PATCH 10/15] update to allow support for idx.namespaces. only and not idx. --- pinecone/db_data/index.py | 20 ----- pinecone/db_data/index_asyncio.py | 20 ----- pinecone/db_data/index_asyncio_interface.py | 78 +------------------ pinecone/db_data/interfaces.py | 73 +---------------- tests/integration/data/test_namespace.py | 38 +++++---- .../data_asyncio/test_namespace_asyncio.py | 36 ++++----- 6 files changed, 37 insertions(+), 228 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 9256eb5ef..f256874c7 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -15,8 +15,6 @@ UpsertResponse, ListResponse, SearchRecordsResponse, - ListNamespacesResponse, - NamespaceDescription, ) from .dataclasses import Vector, SparseValues, FetchResponse, SearchQuery, SearchRerank from .interfaces import IndexInterface @@ -628,21 +626,3 @@ def cancel_import(self, id: str): id (str): The id of the import operation to cancel. """ return self.bulk_import.cancel(id=id) - - @validate_and_convert_errors - def describe_namespace(self, namespace: str) -> "NamespaceDescription": - return self.namespace.describe(namespace=namespace) - - @validate_and_convert_errors - def delete_namespace(self, namespace: str) -> Dict[str, Any]: - return self.namespace.delete(namespace=namespace) - - @validate_and_convert_errors - def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: - return self.namespace.list(**kwargs) - - @validate_and_convert_errors - def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None - ) -> ListNamespacesResponse: - return self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 0c8fe8e48..cfa61c9cc 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -22,8 +22,6 @@ DeleteRequest, ListResponse, SearchRecordsResponse, - ListNamespacesResponse, - NamespaceDescription, ) from ..utils import ( @@ -668,23 +666,5 @@ async def cancel_import(self, id: str): """ return await self.bulk_import.cancel(id=id) - @validate_and_convert_errors - async def describe_namespace(self, namespace: str) -> "NamespaceDescription": - return await self.namespace.describe(namespace=namespace) - - @validate_and_convert_errors - async def delete_namespace(self, namespace: str) -> Dict[str, Any]: - return await self.namespace.delete(namespace=namespace) - - @validate_and_convert_errors - async def list_namespaces(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: - async for namespace in self.namespace.list(**kwargs): - yield namespace - - @validate_and_convert_errors - async def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None - ) -> ListNamespacesResponse: - return await self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) IndexAsyncio = _IndexAsyncio diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index ed57eb801..73ce3f0c1 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, List, Optional, Dict, Any, AsyncIterator +from typing import Union, List, Optional, Dict, Any from pinecone.core.openapi.db_data.models import ( FetchResponse, @@ -10,8 +10,6 @@ ListResponse, SparseValues, SearchRecordsResponse, - NamespaceDescription, - ListNamespacesResponse, ) from .query_results_aggregator import QueryNamespacesResults from .types import ( @@ -812,77 +810,3 @@ async def search_records( ) -> SearchRecordsResponse: """Alias of the search() method.""" pass - - @abstractmethod - async def describe_namespace( - self, - namespace: str - ) -> NamespaceDescription: - """Describe a namespace within an index, showing the vector count within the namespace. - - Args: - namespace (str): The namespace to describe - - Returns: - NamespaceDescription: Information about the namespace including vector count - """ - pass - - @abstractmethod - async def delete_namespace( - self, - namespace: str - ) -> Dict[str, Any]: - """Delete a namespace from an index. - - Args: - namespace (str): The namespace to delete - - Returns: - Dict[str, Any]: Response from the delete operation - """ - pass - - @abstractmethod - async def list_namespaces( - self, **kwargs - ) -> AsyncIterator[ListNamespacesResponse]: - """List all namespaces in an index. This method automatically handles pagination to return all results. - - Args: - limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] - - Returns: - `ListNamespacesResponse`: Object containing the list of namespaces. - - Examples: - >>> async for namespace in index.list_namespaces(limit=5): - ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") - Namespace: namespace1, Vector count: 1000 - Namespace: namespace2, Vector count: 2000 - """ - pass - - @abstractmethod - async def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None - ) -> ListNamespacesResponse: - """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. - - Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. - - Args: - limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] - pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned - in the response if additional results are available. [optional] - - Returns: - `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. - - Examples: - >>> results = await index.list_namespaces_paginated(limit=5) - >>> results.pagination.next - eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 - >>> next_results = await index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) - """ - pass \ No newline at end of file diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index b35703e5e..d22d03d7f 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, List, Optional, Dict, Any, Iterator +from typing import Union, List, Optional, Dict, Any from pinecone.core.openapi.db_data.models import ( FetchResponse, @@ -10,8 +10,6 @@ ListResponse, SparseValues, SearchRecordsResponse, - NamespaceDescription, - ListNamespacesResponse, ) from .query_results_aggregator import QueryNamespacesResults from multiprocessing.pool import ApplyResult @@ -792,72 +790,3 @@ def list(self, **kwargs): namespace (Optional[str]): The namespace to fetch vectors from. If not specified, the default namespace is used. [optional] """ pass - - @abstractmethod - def describe_namespace(self, namespace: str) -> NamespaceDescription: - """Describe a namespace within an index, showing the vector count within the namespace. - - Args: - namespace (str): The namespace to describe - **kwargs: Additional arguments to pass to the API call - - Returns: - NamespaceDescription: Information about the namespace including vector count - """ - pass - - @abstractmethod - def delete_namespace(self, namespace: str) -> Dict[str, Any]: - """Delete a namespace from an index. - - Args: - namespace (str): The namespace to delete - **kwargs: Additional arguments to pass to the API call - - Returns: - Dict[str, Any]: Response from the delete operation - """ - pass - - @abstractmethod - def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: - """List all namespaces in an index. This method automatically handles pagination to return all results. - - Args: - limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] - - Returns: - `ListNamespacesResponse`: Object containing the list of namespaces. - - Examples: - >>> results = list(index.list_namespaces(limit=5)) - >>> for namespace in results: - ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") - Namespace: namespace1, Vector count: 1000 - Namespace: namespace2, Vector count: 2000 - """ - pass - - @abstractmethod - def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None - ) -> ListNamespacesResponse: - """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. - - Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. - - Args: - limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] - pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned - in the response if additional results are available. [optional] - - Returns: - `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. - - Examples: - >>> results = index.list_namespaces_paginated(limit=5) - >>> results.pagination.next - eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 - >>> next_results = index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) - """ - pass \ No newline at end of file diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 20b757284..8a8bc6e45 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -17,7 +17,7 @@ def setup_namespace_data(index, namespace: str, num_vectors: int = 2): def verify_namespace_exists(index, namespace: str) -> bool: """Helper function to verify if a namespace exists""" try: - index.describe_namespace(namespace) + index.namespace.describe(namespace) return True except Exception: return False @@ -27,12 +27,12 @@ def delete_all_namespaces(index): """Helper function to delete all namespaces in an index""" try: # Get all namespaces - namespaces = list(index.list_namespaces()) + namespaces = list(index.namespace.list()) # Delete each namespace for namespace in namespaces: try: - index.delete_namespace(namespace.name) + index.namespace.delete(namespace.name) except Exception as e: print(f"Error deleting namespace {namespace.name}: {e}") @@ -41,6 +41,7 @@ def delete_all_namespaces(index): except Exception as e: print(f"Error in delete_all_namespaces: {e}") + @pytest.mark.skipif( os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added" ) @@ -53,7 +54,7 @@ def test_describe_namespace(self, idx): try: # Test describe - description = idx.describe_namespace(test_namespace) + description = idx.namespace.describe(test_namespace) assert isinstance(description, NamespaceDescription) assert description.name == test_namespace finally: @@ -70,7 +71,7 @@ def test_delete_namespace(self, idx): assert verify_namespace_exists(idx, test_namespace) # Delete namespace - idx.delete_namespace(test_namespace) + idx.namespace.delete(test_namespace) # Wait for namespace to be deleted time.sleep(10) @@ -87,7 +88,7 @@ def test_list_namespaces(self, idx): try: # Get all namespaces - namespaces = list(idx.list_namespaces()) + namespaces = list(idx.namespace.list()) # Verify results assert len(namespaces) == len(test_namespaces) @@ -98,8 +99,8 @@ def test_list_namespaces(self, idx): # Verify each namespace has correct structure for ns in namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, 'name') - assert hasattr(ns, 'vector_count') + assert hasattr(ns, "name") + assert hasattr(ns, "vector_count") finally: # Delete all namespaces before next test is run delete_all_namespaces(idx) @@ -113,20 +114,19 @@ def test_list_namespaces_with_limit(self, idx): try: # Get namespaces with limit - namespaces = list(idx.list_namespaces(limit=2)) + namespaces = list(idx.namespace.list(limit=2)) # Verify results assert len(namespaces) >= 2 # Should get at least 2 namespaces for ns in namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, 'name') - assert hasattr(ns, 'vector_count') + assert hasattr(ns, "name") + assert hasattr(ns, "vector_count") finally: # Delete all namespaces before next test is run delete_all_namespaces(idx) - def test_list_namespaces_paginated(self, idx): """Test listing namespaces with pagination""" # Create multiple test namespaces @@ -136,24 +136,22 @@ def test_list_namespaces_paginated(self, idx): try: # Get first page - response = idx.list_namespaces_paginated(limit=2) + response = idx.namespace.list_paginated(limit=2) assert len(response.namespaces) == 2 assert response.pagination.next is not None # Get second page - next_response = idx.list_namespaces_paginated( - limit=2, - pagination_token=response.pagination.next + next_response = idx.namespace.list_paginated( + limit=2, pagination_token=response.pagination.next ) assert len(next_response.namespaces) == 2 assert next_response.pagination.next is not None # Get final page - final_response = idx.list_namespaces_paginated( - limit=2, - pagination_token=next_response.pagination.next + final_response = idx.namespace.list_paginated( + limit=2, pagination_token=next_response.pagination.next ) assert len(final_response.namespaces) == 1 assert final_response.pagination is None finally: - delete_all_namespaces(idx) \ No newline at end of file + delete_all_namespaces(idx) diff --git a/tests/integration/data_asyncio/test_namespace_asyncio.py b/tests/integration/data_asyncio/test_namespace_asyncio.py index 9efcbad03..81f441872 100644 --- a/tests/integration/data_asyncio/test_namespace_asyncio.py +++ b/tests/integration/data_asyncio/test_namespace_asyncio.py @@ -16,7 +16,7 @@ async def setup_namespace_data(index, namespace: str, num_vectors: int = 2): async def verify_namespace_exists(index, namespace: str) -> bool: """Helper function to verify if a namespace exists""" try: - await index.describe_namespace(namespace) + await index.namespace.describe(namespace) return True except Exception: return False @@ -26,12 +26,12 @@ async def delete_all_namespaces(index): """Helper function to delete all namespaces in an index""" try: # Get all namespaces - namespaces = await index.list_namespaces_paginated() + namespaces = await index.namespace.list_paginated() # Delete each namespace for namespace in namespaces.namespaces: try: - await index.delete_namespace(namespace.name) + await index.namespace.delete(namespace.name) except Exception as e: print(f"Error deleting namespace {namespace.name}: {e}") @@ -53,7 +53,7 @@ async def test_describe_namespace(self, index_host): try: # Test describe - description = await asyncio_idx.describe_namespace(test_namespace) + description = await asyncio_idx.namespace.describe(test_namespace) assert isinstance(description, NamespaceDescription) assert description.name == test_namespace finally: @@ -72,7 +72,7 @@ async def test_delete_namespace(self, index_host): assert await verify_namespace_exists(asyncio_idx, test_namespace) # Delete namespace - await asyncio_idx.delete_namespace(test_namespace) + await asyncio_idx.namespace.delete(test_namespace) # Wait for namespace to be deleted await asyncio.sleep(10) @@ -91,7 +91,7 @@ async def test_list_namespaces(self, index_host): try: # Get all namespaces - namespaces = await asyncio_idx.list_namespaces_paginated() + namespaces = await asyncio_idx.namespace.list_paginated() # Verify results assert len(namespaces.namespaces) >= len(test_namespaces) @@ -102,8 +102,8 @@ async def test_list_namespaces(self, index_host): # Verify each namespace has correct structure for ns in namespaces.namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, 'name') - assert hasattr(ns, 'vector_count') + assert hasattr(ns, "name") + assert hasattr(ns, "vector_count") finally: # Delete all namespaces before next test is run await delete_all_namespaces(asyncio_idx) @@ -119,14 +119,14 @@ async def test_list_namespaces_with_limit(self, index_host): try: # Get namespaces with limit - namespaces = await asyncio_idx.list_namespaces_paginated(limit=2) + namespaces = await asyncio_idx.namespace.list_paginated(limit=2) # Verify results assert len(namespaces.namespaces) == 2 # Should get exactly 2 namespaces for ns in namespaces.namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, 'name') - assert hasattr(ns, 'vector_count') + assert hasattr(ns, "name") + assert hasattr(ns, "vector_count") finally: # Delete all namespaces before next test is run await delete_all_namespaces(asyncio_idx) @@ -142,25 +142,23 @@ async def test_list_namespaces_paginated(self, index_host): try: # Get first page - response = await asyncio_idx.list_namespaces_paginated(limit=2) + response = await asyncio_idx.namespace.list_paginated(limit=2) assert len(response.namespaces) == 2 assert response.pagination.next is not None # Get second page - next_response = await asyncio_idx.list_namespaces_paginated( - limit=2, - pagination_token=response.pagination.next + next_response = await asyncio_idx.namespace.list_paginated( + limit=2, pagination_token=response.pagination.next ) assert len(next_response.namespaces) == 2 assert next_response.pagination.next is not None # Get final page - final_response = await asyncio_idx.list_namespaces_paginated( - limit=2, - pagination_token=next_response.pagination.next + final_response = await asyncio_idx.namespace.list_paginated( + limit=2, pagination_token=next_response.pagination.next ) assert len(final_response.namespaces) == 1 assert final_response.pagination is None finally: # Delete all namespaces before next test is run - await delete_all_namespaces(asyncio_idx) \ No newline at end of file + await delete_all_namespaces(asyncio_idx) From 71569b8c484017c6a2dbba80bf73123c8be01d5f Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Mon, 9 Jun 2025 10:40:57 -0400 Subject: [PATCH 11/15] Revert "update to allow support for idx.namespaces. only and not idx." This reverts commit 6a011e863bf81e2498f0b6ee17e66ad21d968cd3. --- pinecone/db_data/index.py | 20 +++++ pinecone/db_data/index_asyncio.py | 20 +++++ pinecone/db_data/index_asyncio_interface.py | 78 ++++++++++++++++++- pinecone/db_data/interfaces.py | 73 ++++++++++++++++- tests/integration/data/test_namespace.py | 38 ++++----- .../data_asyncio/test_namespace_asyncio.py | 36 +++++---- 6 files changed, 228 insertions(+), 37 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index f256874c7..9256eb5ef 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -15,6 +15,8 @@ UpsertResponse, ListResponse, SearchRecordsResponse, + ListNamespacesResponse, + NamespaceDescription, ) from .dataclasses import Vector, SparseValues, FetchResponse, SearchQuery, SearchRerank from .interfaces import IndexInterface @@ -626,3 +628,21 @@ def cancel_import(self, id: str): id (str): The id of the import operation to cancel. """ return self.bulk_import.cancel(id=id) + + @validate_and_convert_errors + def describe_namespace(self, namespace: str) -> "NamespaceDescription": + return self.namespace.describe(namespace=namespace) + + @validate_and_convert_errors + def delete_namespace(self, namespace: str) -> Dict[str, Any]: + return self.namespace.delete(namespace=namespace) + + @validate_and_convert_errors + def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: + return self.namespace.list(**kwargs) + + @validate_and_convert_errors + def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + return self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index cfa61c9cc..0c8fe8e48 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -22,6 +22,8 @@ DeleteRequest, ListResponse, SearchRecordsResponse, + ListNamespacesResponse, + NamespaceDescription, ) from ..utils import ( @@ -666,5 +668,23 @@ async def cancel_import(self, id: str): """ return await self.bulk_import.cancel(id=id) + @validate_and_convert_errors + async def describe_namespace(self, namespace: str) -> "NamespaceDescription": + return await self.namespace.describe(namespace=namespace) + + @validate_and_convert_errors + async def delete_namespace(self, namespace: str) -> Dict[str, Any]: + return await self.namespace.delete(namespace=namespace) + + @validate_and_convert_errors + async def list_namespaces(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: + async for namespace in self.namespace.list(**kwargs): + yield namespace + + @validate_and_convert_errors + async def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + return await self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) IndexAsyncio = _IndexAsyncio diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index 73ce3f0c1..ed57eb801 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, List, Optional, Dict, Any +from typing import Union, List, Optional, Dict, Any, AsyncIterator from pinecone.core.openapi.db_data.models import ( FetchResponse, @@ -10,6 +10,8 @@ ListResponse, SparseValues, SearchRecordsResponse, + NamespaceDescription, + ListNamespacesResponse, ) from .query_results_aggregator import QueryNamespacesResults from .types import ( @@ -810,3 +812,77 @@ async def search_records( ) -> SearchRecordsResponse: """Alias of the search() method.""" pass + + @abstractmethod + async def describe_namespace( + self, + namespace: str + ) -> NamespaceDescription: + """Describe a namespace within an index, showing the vector count within the namespace. + + Args: + namespace (str): The namespace to describe + + Returns: + NamespaceDescription: Information about the namespace including vector count + """ + pass + + @abstractmethod + async def delete_namespace( + self, + namespace: str + ) -> Dict[str, Any]: + """Delete a namespace from an index. + + Args: + namespace (str): The namespace to delete + + Returns: + Dict[str, Any]: Response from the delete operation + """ + pass + + @abstractmethod + async def list_namespaces( + self, **kwargs + ) -> AsyncIterator[ListNamespacesResponse]: + """List all namespaces in an index. This method automatically handles pagination to return all results. + + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces. + + Examples: + >>> async for namespace in index.list_namespaces(limit=5): + ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") + Namespace: namespace1, Vector count: 1000 + Namespace: namespace2, Vector count: 2000 + """ + pass + + @abstractmethod + async def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. + + Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. + + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned + in the response if additional results are available. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + + Examples: + >>> results = await index.list_namespaces_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = await index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) + """ + pass \ No newline at end of file diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index d22d03d7f..b35703e5e 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Union, List, Optional, Dict, Any +from typing import Union, List, Optional, Dict, Any, Iterator from pinecone.core.openapi.db_data.models import ( FetchResponse, @@ -10,6 +10,8 @@ ListResponse, SparseValues, SearchRecordsResponse, + NamespaceDescription, + ListNamespacesResponse, ) from .query_results_aggregator import QueryNamespacesResults from multiprocessing.pool import ApplyResult @@ -790,3 +792,72 @@ def list(self, **kwargs): namespace (Optional[str]): The namespace to fetch vectors from. If not specified, the default namespace is used. [optional] """ pass + + @abstractmethod + def describe_namespace(self, namespace: str) -> NamespaceDescription: + """Describe a namespace within an index, showing the vector count within the namespace. + + Args: + namespace (str): The namespace to describe + **kwargs: Additional arguments to pass to the API call + + Returns: + NamespaceDescription: Information about the namespace including vector count + """ + pass + + @abstractmethod + def delete_namespace(self, namespace: str) -> Dict[str, Any]: + """Delete a namespace from an index. + + Args: + namespace (str): The namespace to delete + **kwargs: Additional arguments to pass to the API call + + Returns: + Dict[str, Any]: Response from the delete operation + """ + pass + + @abstractmethod + def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: + """List all namespaces in an index. This method automatically handles pagination to return all results. + + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces. + + Examples: + >>> results = list(index.list_namespaces(limit=5)) + >>> for namespace in results: + ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") + Namespace: namespace1, Vector count: 1000 + Namespace: namespace2, Vector count: 2000 + """ + pass + + @abstractmethod + def list_namespaces_paginated( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None + ) -> ListNamespacesResponse: + """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. + + Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. + + Args: + limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] + pagination_token (Optional[str]): A token needed to fetch the next page of results. This token is returned + in the response if additional results are available. [optional] + + Returns: + `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + + Examples: + >>> results = index.list_namespaces_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) + """ + pass \ No newline at end of file diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 8a8bc6e45..20b757284 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -17,7 +17,7 @@ def setup_namespace_data(index, namespace: str, num_vectors: int = 2): def verify_namespace_exists(index, namespace: str) -> bool: """Helper function to verify if a namespace exists""" try: - index.namespace.describe(namespace) + index.describe_namespace(namespace) return True except Exception: return False @@ -27,12 +27,12 @@ def delete_all_namespaces(index): """Helper function to delete all namespaces in an index""" try: # Get all namespaces - namespaces = list(index.namespace.list()) + namespaces = list(index.list_namespaces()) # Delete each namespace for namespace in namespaces: try: - index.namespace.delete(namespace.name) + index.delete_namespace(namespace.name) except Exception as e: print(f"Error deleting namespace {namespace.name}: {e}") @@ -41,7 +41,6 @@ def delete_all_namespaces(index): except Exception as e: print(f"Error in delete_all_namespaces: {e}") - @pytest.mark.skipif( os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added" ) @@ -54,7 +53,7 @@ def test_describe_namespace(self, idx): try: # Test describe - description = idx.namespace.describe(test_namespace) + description = idx.describe_namespace(test_namespace) assert isinstance(description, NamespaceDescription) assert description.name == test_namespace finally: @@ -71,7 +70,7 @@ def test_delete_namespace(self, idx): assert verify_namespace_exists(idx, test_namespace) # Delete namespace - idx.namespace.delete(test_namespace) + idx.delete_namespace(test_namespace) # Wait for namespace to be deleted time.sleep(10) @@ -88,7 +87,7 @@ def test_list_namespaces(self, idx): try: # Get all namespaces - namespaces = list(idx.namespace.list()) + namespaces = list(idx.list_namespaces()) # Verify results assert len(namespaces) == len(test_namespaces) @@ -99,8 +98,8 @@ def test_list_namespaces(self, idx): # Verify each namespace has correct structure for ns in namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, "name") - assert hasattr(ns, "vector_count") + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') finally: # Delete all namespaces before next test is run delete_all_namespaces(idx) @@ -114,19 +113,20 @@ def test_list_namespaces_with_limit(self, idx): try: # Get namespaces with limit - namespaces = list(idx.namespace.list(limit=2)) + namespaces = list(idx.list_namespaces(limit=2)) # Verify results assert len(namespaces) >= 2 # Should get at least 2 namespaces for ns in namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, "name") - assert hasattr(ns, "vector_count") + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') finally: # Delete all namespaces before next test is run delete_all_namespaces(idx) + def test_list_namespaces_paginated(self, idx): """Test listing namespaces with pagination""" # Create multiple test namespaces @@ -136,22 +136,24 @@ def test_list_namespaces_paginated(self, idx): try: # Get first page - response = idx.namespace.list_paginated(limit=2) + response = idx.list_namespaces_paginated(limit=2) assert len(response.namespaces) == 2 assert response.pagination.next is not None # Get second page - next_response = idx.namespace.list_paginated( - limit=2, pagination_token=response.pagination.next + next_response = idx.list_namespaces_paginated( + limit=2, + pagination_token=response.pagination.next ) assert len(next_response.namespaces) == 2 assert next_response.pagination.next is not None # Get final page - final_response = idx.namespace.list_paginated( - limit=2, pagination_token=next_response.pagination.next + final_response = idx.list_namespaces_paginated( + limit=2, + pagination_token=next_response.pagination.next ) assert len(final_response.namespaces) == 1 assert final_response.pagination is None finally: - delete_all_namespaces(idx) + delete_all_namespaces(idx) \ No newline at end of file diff --git a/tests/integration/data_asyncio/test_namespace_asyncio.py b/tests/integration/data_asyncio/test_namespace_asyncio.py index 81f441872..9efcbad03 100644 --- a/tests/integration/data_asyncio/test_namespace_asyncio.py +++ b/tests/integration/data_asyncio/test_namespace_asyncio.py @@ -16,7 +16,7 @@ async def setup_namespace_data(index, namespace: str, num_vectors: int = 2): async def verify_namespace_exists(index, namespace: str) -> bool: """Helper function to verify if a namespace exists""" try: - await index.namespace.describe(namespace) + await index.describe_namespace(namespace) return True except Exception: return False @@ -26,12 +26,12 @@ async def delete_all_namespaces(index): """Helper function to delete all namespaces in an index""" try: # Get all namespaces - namespaces = await index.namespace.list_paginated() + namespaces = await index.list_namespaces_paginated() # Delete each namespace for namespace in namespaces.namespaces: try: - await index.namespace.delete(namespace.name) + await index.delete_namespace(namespace.name) except Exception as e: print(f"Error deleting namespace {namespace.name}: {e}") @@ -53,7 +53,7 @@ async def test_describe_namespace(self, index_host): try: # Test describe - description = await asyncio_idx.namespace.describe(test_namespace) + description = await asyncio_idx.describe_namespace(test_namespace) assert isinstance(description, NamespaceDescription) assert description.name == test_namespace finally: @@ -72,7 +72,7 @@ async def test_delete_namespace(self, index_host): assert await verify_namespace_exists(asyncio_idx, test_namespace) # Delete namespace - await asyncio_idx.namespace.delete(test_namespace) + await asyncio_idx.delete_namespace(test_namespace) # Wait for namespace to be deleted await asyncio.sleep(10) @@ -91,7 +91,7 @@ async def test_list_namespaces(self, index_host): try: # Get all namespaces - namespaces = await asyncio_idx.namespace.list_paginated() + namespaces = await asyncio_idx.list_namespaces_paginated() # Verify results assert len(namespaces.namespaces) >= len(test_namespaces) @@ -102,8 +102,8 @@ async def test_list_namespaces(self, index_host): # Verify each namespace has correct structure for ns in namespaces.namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, "name") - assert hasattr(ns, "vector_count") + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') finally: # Delete all namespaces before next test is run await delete_all_namespaces(asyncio_idx) @@ -119,14 +119,14 @@ async def test_list_namespaces_with_limit(self, index_host): try: # Get namespaces with limit - namespaces = await asyncio_idx.namespace.list_paginated(limit=2) + namespaces = await asyncio_idx.list_namespaces_paginated(limit=2) # Verify results assert len(namespaces.namespaces) == 2 # Should get exactly 2 namespaces for ns in namespaces.namespaces: assert isinstance(ns, NamespaceDescription) - assert hasattr(ns, "name") - assert hasattr(ns, "vector_count") + assert hasattr(ns, 'name') + assert hasattr(ns, 'vector_count') finally: # Delete all namespaces before next test is run await delete_all_namespaces(asyncio_idx) @@ -142,23 +142,25 @@ async def test_list_namespaces_paginated(self, index_host): try: # Get first page - response = await asyncio_idx.namespace.list_paginated(limit=2) + response = await asyncio_idx.list_namespaces_paginated(limit=2) assert len(response.namespaces) == 2 assert response.pagination.next is not None # Get second page - next_response = await asyncio_idx.namespace.list_paginated( - limit=2, pagination_token=response.pagination.next + next_response = await asyncio_idx.list_namespaces_paginated( + limit=2, + pagination_token=response.pagination.next ) assert len(next_response.namespaces) == 2 assert next_response.pagination.next is not None # Get final page - final_response = await asyncio_idx.namespace.list_paginated( - limit=2, pagination_token=next_response.pagination.next + final_response = await asyncio_idx.list_namespaces_paginated( + limit=2, + pagination_token=next_response.pagination.next ) assert len(final_response.namespaces) == 1 assert final_response.pagination is None finally: # Delete all namespaces before next test is run - await delete_all_namespaces(asyncio_idx) + await delete_all_namespaces(asyncio_idx) \ No newline at end of file From 7f6f0c31b1ddab37d2252bfa927fd448f8f44df9 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 13 Jun 2025 15:46:22 -0400 Subject: [PATCH 12/15] address code review comments --- pinecone/__init__.py | 1 + pinecone/__init__.pyi | 2 ++ pinecone/db_data/index.py | 6 ++-- pinecone/db_data/index_asyncio.py | 6 ++-- pinecone/db_data/index_asyncio_interface.py | 24 +++++++------ pinecone/db_data/interfaces.py | 32 +++++++++-------- .../resources/asyncio/namespace_asyncio.py | 33 +++++++++-------- pinecone/db_data/resources/sync/namespace.py | 35 +++++++++---------- .../sync/namespace_request_factory.py | 6 ++-- tests/integration/data/test_namespace.py | 8 +++-- .../data_asyncio/test_namespace_asyncio.py | 8 +++-- 11 files changed, 89 insertions(+), 72 deletions(-) diff --git a/pinecone/__init__.py b/pinecone/__init__.py index 2e55fe84f..69f1d84f5 100644 --- a/pinecone/__init__.py +++ b/pinecone/__init__.py @@ -55,6 +55,7 @@ "QueryResponse": ("pinecone.db_data.models", "QueryResponse"), "UpsertResponse": ("pinecone.db_data.models", "UpsertResponse"), "UpdateRequest": ("pinecone.db_data.models", "UpdateRequest"), + "NamespaceDescription": ("pinecone.core.openapi.db_data.models", "NamespaceDescription"), "ImportErrorMode": ("pinecone.db_data.resources.sync.bulk_import", "ImportErrorMode"), "VectorDictionaryMissingKeysError": ( "pinecone.db_data.errors", diff --git a/pinecone/__init__.pyi b/pinecone/__init__.pyi index 06de82ed5..cf4cc0b70 100644 --- a/pinecone/__init__.pyi +++ b/pinecone/__init__.pyi @@ -46,6 +46,7 @@ from pinecone.db_data.models import ( UpsertResponse, UpdateRequest, ) +from pinecone.core.openapi.db_data.models import NamespaceDescription from pinecone.db_data.resources.sync.bulk_import import ImportErrorMode from pinecone.db_data.errors import ( VectorDictionaryMissingKeysError, @@ -130,6 +131,7 @@ __all__ = [ "QueryResponse", "UpsertResponse", "UpdateRequest", + "NamespaceDescription", "ImportErrorMode", # Error classes "VectorDictionaryMissingKeysError", diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 9256eb5ef..153a0f9b6 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -638,11 +638,13 @@ def delete_namespace(self, namespace: str) -> Dict[str, Any]: return self.namespace.delete(namespace=namespace) @validate_and_convert_errors - def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: + def list_namespaces( + self, limit: Optional[int] = None, **kwargs + ) -> Iterator[ListNamespacesResponse]: return self.namespace.list(**kwargs) @validate_and_convert_errors def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: return self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 0c8fe8e48..1c5d0fb9d 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -677,13 +677,15 @@ async def delete_namespace(self, namespace: str) -> Dict[str, Any]: return await self.namespace.delete(namespace=namespace) @validate_and_convert_errors - async def list_namespaces(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: + async def list_namespaces( + self, limit: Optional[int] = None, **kwargs + ) -> AsyncIterator[ListNamespacesResponse]: async for namespace in self.namespace.list(**kwargs): yield namespace @validate_and_convert_errors async def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: return await self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index ed57eb801..5c1f5d2af 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -853,13 +853,14 @@ async def list_namespaces( limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] Returns: - `ListNamespacesResponse`: Object containing the list of namespaces. + ``ListNamespacesResponse``: Object containing the list of namespaces. Examples: - >>> async for namespace in index.list_namespaces(limit=5): - ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") - Namespace: namespace1, Vector count: 1000 - Namespace: namespace2, Vector count: 2000 + .. code-block:: python + >>> async for namespace in index.list_namespaces(limit=5): + ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") + Namespace: namespace1, Vector count: 1000 + Namespace: namespace2, Vector count: 2000 """ pass @@ -869,7 +870,7 @@ async def list_namespaces_paginated( ) -> ListNamespacesResponse: """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. - Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. + Consider using the ``list_namespaces`` method to avoid having to handle pagination tokens manually. Args: limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] @@ -877,12 +878,13 @@ async def list_namespaces_paginated( in the response if additional results are available. [optional] Returns: - `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + ``ListNamespacesResponse``: Object containing the list of namespaces and pagination information. Examples: - >>> results = await index.list_namespaces_paginated(limit=5) - >>> results.pagination.next - eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 - >>> next_results = await index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) + .. code-block:: python + >>> results = await index.list_namespaces_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = await index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) """ pass \ No newline at end of file diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index b35703e5e..4089ba470 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -820,31 +820,34 @@ def delete_namespace(self, namespace: str) -> Dict[str, Any]: pass @abstractmethod - def list_namespaces(self, **kwargs) -> Iterator[ListNamespacesResponse]: + def list_namespaces( + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs + ) -> Iterator[ListNamespacesResponse]: """List all namespaces in an index. This method automatically handles pagination to return all results. Args: limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] Returns: - `ListNamespacesResponse`: Object containing the list of namespaces. + ``ListNamespacesResponse``: Object containing the list of namespaces. Examples: - >>> results = list(index.list_namespaces(limit=5)) - >>> for namespace in results: - ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") - Namespace: namespace1, Vector count: 1000 - Namespace: namespace2, Vector count: 2000 + .. code-block:: python + >>> results = list(index.list_namespaces(limit=5)) + >>> for namespace in results: + ... print(f"Namespace: {namespace.name}, Vector count: {namespace.vector_count}") + Namespace: namespace1, Vector count: 1000 + Namespace: namespace2, Vector count: 2000 """ pass @abstractmethod def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. - Consider using the `list_namespaces` method to avoid having to handle pagination tokens manually. + Consider using the ``list_namespaces`` method to avoid having to handle pagination tokens manually. Args: limit (Optional[int]): The maximum number of namespaces to return. If unspecified, the server will use a default value. [optional] @@ -852,12 +855,13 @@ def list_namespaces_paginated( in the response if additional results are available. [optional] Returns: - `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + ``ListNamespacesResponse``: Object containing the list of namespaces and pagination information. Examples: - >>> results = index.list_namespaces_paginated(limit=5) - >>> results.pagination.next - eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 - >>> next_results = index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) + .. code-block:: python + >>> results = index.list_namespaces_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = index.list_namespaces_paginated(limit=5, pagination_token=results.pagination.next) """ pass \ No newline at end of file diff --git a/pinecone/db_data/resources/asyncio/namespace_asyncio.py b/pinecone/db_data/resources/asyncio/namespace_asyncio.py index 5a6af282f..66d9fbc9d 100644 --- a/pinecone/db_data/resources/asyncio/namespace_asyncio.py +++ b/pinecone/db_data/resources/asyncio/namespace_asyncio.py @@ -24,7 +24,7 @@ async def describe(self, namespace: str) -> NamespaceDescription: namespace (str): The namespace to describe Returns: - `NamespaceDescription`: Information about the namespace including vector count + ``NamespaceDescription``: Information about the namespace including vector count Describe a namespace within an index, showing the vector count within the namespace. """ @@ -41,7 +41,7 @@ async def delete(self, namespace: str): args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) return await self.__namespace_operations_api.delete_namespace(**args) - async def list(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: + async def list(self, limit: Optional[int] = None, **kwargs) -> AsyncIterator[ListNamespacesResponse]: """ Args: limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional] @@ -50,25 +50,23 @@ async def list(self, **kwargs) -> AsyncIterator[ListNamespacesResponse]: Returns: Returns an async generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can - easily iterate over all results. The `list` method accepts all of the same arguments as list_paginated + easily iterate over all results. The ``list`` method accepts all of the same arguments as list_paginated - ```python - async for namespace in index.list_namespaces(): - print(namespace) - ``` + .. code-block:: python + async for namespace in index.list_namespaces(): + print(namespace) You can convert the generator into a list by using an async list comprehension: - ```python - namespaces = [namespace async for namespace in index.list_namespaces()] - ``` + .. code-block:: python + namespaces = [namespace async for namespace in index.list_namespaces()] You should be cautious with this approach because it will fetch all namespaces at once, which could be a large number of network calls and a lot of memory to hold the results. """ done = False while not done: - results = await self.list_paginated(**kwargs) + results = await self.list_paginated(limit, **kwargs) if results.namespaces is not None and len(results.namespaces) > 0: for namespace in results.namespaces: yield namespace @@ -88,17 +86,18 @@ async def list_paginated( in the response if additional results are available. [optional] Returns: - `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + ``ListNamespacesResponse``: Object containing the list of namespaces and pagination information. List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. - Consider using the `list` method to avoid having to handle pagination tokens manually. + Consider using the ``list`` method to avoid having to handle pagination tokens manually. Examples: - >>> results = await index.list_paginated(limit=5) - >>> results.pagination.next - eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 - >>> next_results = await index.list_paginated(limit=5, pagination_token=results.pagination.next) + .. code-block:: python + >>> results = await index.list_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = await index.list_paginated(limit=5, pagination_token=results.pagination.next) """ args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) return await self.__namespace_operations_api.list_namespaces_operation(**args) \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index aabf6da12..f4eaac7dd 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -40,7 +40,7 @@ def describe(self, namespace: str) -> NamespaceDescription: namespace (str): The namespace to describe Returns: - `NamespaceDescription`: Information about the namespace including vector count + ``NamespaceDescription``: Information about the namespace including vector count Describe a namespace within an index, showing the vector count within the namespace. """ @@ -57,7 +57,7 @@ def delete(self, namespace: str): args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) return self.__namespace_operations_api.delete_namespace(**args) - def list(self, **kwargs) -> Iterator[ListNamespacesResponse]: + def list(self, limit: Optional[int] = None, **kwargs) -> Iterator[ListNamespacesResponse]: """ Args: limit (Optional[int]): The maximum number of namespaces to fetch in each network call. If unspecified, the server will use a default value. [optional] @@ -66,25 +66,23 @@ def list(self, **kwargs) -> Iterator[ListNamespacesResponse]: Returns: Returns a generator that yields each namespace. It automatically handles pagination tokens on your behalf so you can - easily iterate over all results. The `list` method accepts all of the same arguments as list_paginated + easily iterate over all results. The ``list`` method accepts all of the same arguments as list_paginated - ```python - for namespace in index.list_namespaces(): - print(namespace) - ``` + .. code-block:: python + for namespace in index.list_namespaces(): + print(namespace) - You can convert the generator into a list by wrapping the generator in a call to the built-in `list` function: + You can convert the generator into a list by wrapping the generator in a call to the built-in ``list`` function: - ```python - namespaces = list(index.list_namespaces()) - ``` + .. code-block:: python + namespaces = list(index.list_namespaces()) You should be cautious with this approach because it will fetch all namespaces at once, which could be a large number of network calls and a lot of memory to hold the results. """ done = False while not done: - results = self.list_paginated(**kwargs) + results = self.list_paginated(limit, **kwargs) if results.namespaces is not None and len(results.namespaces) > 0: for namespace in results.namespaces: yield namespace @@ -104,17 +102,18 @@ def list_paginated( in the response if additional results are available. [optional] Returns: - `ListNamespacesResponse`: Object containing the list of namespaces and pagination information. + ``ListNamespacesResponse``: Object containing the list of namespaces and pagination information. List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. - Consider using the `list` method to avoid having to handle pagination tokens manually. + Consider using the ``list`` method to avoid having to handle pagination tokens manually. Examples: - >>> results = index.list_paginated(limit=5) - >>> results.pagination.next - eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 - >>> next_results = index.list_paginated(limit=5, pagination_token=results.pagination.next) + .. code-block:: python + >>> results = index.list_paginated(limit=5) + >>> results.pagination.next + eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 + >>> next_results = index.list_paginated(limit=5, pagination_token=results.pagination.next) """ args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) return self.__namespace_operations_api.list_namespaces_operation(**args) \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace_request_factory.py b/pinecone/db_data/resources/sync/namespace_request_factory.py index c6b639001..6f056fabd 100644 --- a/pinecone/db_data/resources/sync/namespace_request_factory.py +++ b/pinecone/db_data/resources/sync/namespace_request_factory.py @@ -14,12 +14,14 @@ class DeleteNamespaceArgs(TypedDict, total=False): class NamespaceRequestFactory: @staticmethod def describe_namespace_args(namespace: str) -> DescribeNamespaceArgs: + if not isinstance(namespace, str): + raise ValueError('namespace must be string') return {"namespace": namespace} @staticmethod def delete_namespace_args(namespace: str) -> DeleteNamespaceArgs: - if isinstance(namespace, int): - namespace = str(namespace) + if not isinstance(namespace, str): + raise ValueError('namespace must be string') return {"namespace": namespace} @staticmethod diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 20b757284..0c5745b7d 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -1,10 +1,12 @@ import os import time +import logging import pytest -from pinecone.core.openapi.db_data.models import NamespaceDescription +from pinecone import NamespaceDescription +logger = logging.getLogger(__name__) def setup_namespace_data(index, namespace: str, num_vectors: int = 2): """Helper function to set up test data in a namespace""" @@ -34,12 +36,12 @@ def delete_all_namespaces(index): try: index.delete_namespace(namespace.name) except Exception as e: - print(f"Error deleting namespace {namespace.name}: {e}") + logger.error(f"Error deleting namespace {namespace.name}: {e}") # Wait for deletions to complete time.sleep(5) except Exception as e: - print(f"Error in delete_all_namespaces: {e}") + logger.error(f"Error in delete_all_namespaces: {e}") @pytest.mark.skipif( os.getenv("USE_GRPC") == "true", reason="Disable until grpc namespaces support is added" diff --git a/tests/integration/data_asyncio/test_namespace_asyncio.py b/tests/integration/data_asyncio/test_namespace_asyncio.py index 9efcbad03..b2b86a86b 100644 --- a/tests/integration/data_asyncio/test_namespace_asyncio.py +++ b/tests/integration/data_asyncio/test_namespace_asyncio.py @@ -1,9 +1,11 @@ import pytest import asyncio +import logging -from pinecone.core.openapi.db_data.models import NamespaceDescription +from pinecone import NamespaceDescription from tests.integration.data_asyncio.conftest import build_asyncioindex_client +logger = logging.getLogger(__name__) async def setup_namespace_data(index, namespace: str, num_vectors: int = 2): """Helper function to set up test data in a namespace""" @@ -33,12 +35,12 @@ async def delete_all_namespaces(index): try: await index.delete_namespace(namespace.name) except Exception as e: - print(f"Error deleting namespace {namespace.name}: {e}") + logger.error(f"Error deleting namespace {namespace.name}: {e}") # Wait for deletions to complete await asyncio.sleep(5) except Exception as e: - print(f"Error in delete_all_namespaces: {e}") + logger.error(f"Error in delete_all_namespaces: {e}") class TestNamespaceOperationsAsyncio: From dfb4c3a162ad5ce917b6d7ff305bc638760acb51 Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Fri, 13 Jun 2025 16:53:39 -0400 Subject: [PATCH 13/15] add @require_kwargs decorator --- pinecone/db_data/index.py | 9 +++++++-- pinecone/db_data/index_asyncio.py | 7 ++++++- pinecone/db_data/index_asyncio_interface.py | 5 +++++ pinecone/db_data/interfaces.py | 5 +++++ .../resources/asyncio/namespace_asyncio.py | 12 ++++++++---- pinecone/db_data/resources/sync/namespace.py | 12 ++++++++---- .../sync/namespace_request_factory.py | 5 +++-- tests/integration/data/test_namespace.py | 8 ++++---- .../data_asyncio/test_namespace_asyncio.py | 18 ++++++++++-------- 9 files changed, 56 insertions(+), 25 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 153a0f9b6..3710e589f 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -37,6 +37,7 @@ validate_and_convert_errors, filter_dict, PluginAware, + require_kwargs, ) from .query_results_aggregator import QueryResultsAggregator, QueryNamespacesResults from pinecone.openapi_support import OPENAPI_ENDPOINT_PARAMS @@ -630,21 +631,25 @@ def cancel_import(self, id: str): return self.bulk_import.cancel(id=id) @validate_and_convert_errors + @require_kwargs def describe_namespace(self, namespace: str) -> "NamespaceDescription": return self.namespace.describe(namespace=namespace) @validate_and_convert_errors + @require_kwargs def delete_namespace(self, namespace: str) -> Dict[str, Any]: return self.namespace.delete(namespace=namespace) @validate_and_convert_errors + @require_kwargs def list_namespaces( self, limit: Optional[int] = None, **kwargs ) -> Iterator[ListNamespacesResponse]: - return self.namespace.list(**kwargs) + return self.namespace.list(limit=limit, **kwargs) @validate_and_convert_errors + @require_kwargs def list_namespaces_paginated( self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: - return self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) \ No newline at end of file + return self.namespace.list_paginated(limit=limit, pagination_token=pagination_token, **kwargs) \ No newline at end of file diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 1c5d0fb9d..09ae6eeb4 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -31,6 +31,7 @@ parse_non_empty_args, validate_and_convert_errors, filter_dict, + require_kwargs, ) from .types import ( SparseVectorTypedDict, @@ -669,21 +670,25 @@ async def cancel_import(self, id: str): return await self.bulk_import.cancel(id=id) @validate_and_convert_errors + @require_kwargs async def describe_namespace(self, namespace: str) -> "NamespaceDescription": return await self.namespace.describe(namespace=namespace) @validate_and_convert_errors + @require_kwargs async def delete_namespace(self, namespace: str) -> Dict[str, Any]: return await self.namespace.delete(namespace=namespace) @validate_and_convert_errors + @require_kwargs async def list_namespaces( self, limit: Optional[int] = None, **kwargs ) -> AsyncIterator[ListNamespacesResponse]: - async for namespace in self.namespace.list(**kwargs): + async for namespace in self.namespace.list(limit=limit, **kwargs): yield namespace @validate_and_convert_errors + @require_kwargs async def list_namespaces_paginated( self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index 5c1f5d2af..7128e245a 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -25,6 +25,7 @@ SearchRerankTypedDict, ) from .dataclasses import SearchQuery, SearchRerank +from pinecone.utils import require_kwargs class IndexAsyncioInterface(ABC): @@ -814,6 +815,7 @@ async def search_records( pass @abstractmethod + @require_kwargs async def describe_namespace( self, namespace: str @@ -829,6 +831,7 @@ async def describe_namespace( pass @abstractmethod + @require_kwargs async def delete_namespace( self, namespace: str @@ -844,6 +847,7 @@ async def delete_namespace( pass @abstractmethod + @require_kwargs async def list_namespaces( self, **kwargs ) -> AsyncIterator[ListNamespacesResponse]: @@ -865,6 +869,7 @@ async def list_namespaces( pass @abstractmethod + @require_kwargs async def list_namespaces_paginated( self, limit: Optional[int] = None, pagination_token: Optional[str] = None ) -> ListNamespacesResponse: diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index 4089ba470..5f11562ad 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -26,6 +26,7 @@ SearchRerankTypedDict, ) from .dataclasses import SearchQuery, SearchRerank +from pinecone.utils import require_kwargs class IndexInterface(ABC): @@ -794,6 +795,7 @@ def list(self, **kwargs): pass @abstractmethod + @require_kwargs def describe_namespace(self, namespace: str) -> NamespaceDescription: """Describe a namespace within an index, showing the vector count within the namespace. @@ -807,6 +809,7 @@ def describe_namespace(self, namespace: str) -> NamespaceDescription: pass @abstractmethod + @require_kwargs def delete_namespace(self, namespace: str) -> Dict[str, Any]: """Delete a namespace from an index. @@ -820,6 +823,7 @@ def delete_namespace(self, namespace: str) -> Dict[str, Any]: pass @abstractmethod + @require_kwargs def list_namespaces( self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> Iterator[ListNamespacesResponse]: @@ -842,6 +846,7 @@ def list_namespaces( pass @abstractmethod + @require_kwargs def list_namespaces_paginated( self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: diff --git a/pinecone/db_data/resources/asyncio/namespace_asyncio.py b/pinecone/db_data/resources/asyncio/namespace_asyncio.py index 66d9fbc9d..10ab78684 100644 --- a/pinecone/db_data/resources/asyncio/namespace_asyncio.py +++ b/pinecone/db_data/resources/asyncio/namespace_asyncio.py @@ -6,7 +6,7 @@ NamespaceDescription, ) -from pinecone.utils import install_json_repr_override +from pinecone.utils import install_json_repr_override, require_kwargs from ..sync.namespace_request_factory import NamespaceRequestFactory @@ -18,6 +18,7 @@ class NamespaceResourceAsyncio: def __init__(self, api_client) -> None: self.__namespace_operations_api = AsyncioNamespaceOperationsApi(api_client) + @require_kwargs async def describe(self, namespace: str) -> NamespaceDescription: """ Args: @@ -31,6 +32,7 @@ async def describe(self, namespace: str) -> NamespaceDescription: args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace) return await self.__namespace_operations_api.describe_namespace(**args) + @require_kwargs async def delete(self, namespace: str): """ Args: @@ -41,6 +43,7 @@ async def delete(self, namespace: str): args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) return await self.__namespace_operations_api.delete_namespace(**args) + @require_kwargs async def list(self, limit: Optional[int] = None, **kwargs) -> AsyncIterator[ListNamespacesResponse]: """ Args: @@ -66,7 +69,7 @@ async def list(self, limit: Optional[int] = None, **kwargs) -> AsyncIterator[Lis """ done = False while not done: - results = await self.list_paginated(limit, **kwargs) + results = await self.list_paginated(limit=limit, **kwargs) if results.namespaces is not None and len(results.namespaces) > 0: for namespace in results.namespaces: yield namespace @@ -76,8 +79,9 @@ async def list(self, limit: Optional[int] = None, **kwargs) -> AsyncIterator[Lis else: done = True + @require_kwargs async def list_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: """ Args: @@ -99,5 +103,5 @@ async def list_paginated( eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 >>> next_results = await index.list_paginated(limit=5, pagination_token=results.pagination.next) """ - args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) + args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token, **kwargs) return await self.__namespace_operations_api.list_namespaces_operation(**args) \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index f4eaac7dd..80064b589 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -6,7 +6,7 @@ NamespaceDescription, ) -from pinecone.utils import install_json_repr_override, PluginAware +from pinecone.utils import install_json_repr_override, PluginAware, require_kwargs from .namespace_request_factory import NamespaceRequestFactory @@ -34,6 +34,7 @@ def __init__( self.__namespace_operations_api = NamespaceOperationsApi(api_client) super().__init__() + @require_kwargs def describe(self, namespace: str) -> NamespaceDescription: """ Args: @@ -47,6 +48,7 @@ def describe(self, namespace: str) -> NamespaceDescription: args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace) return self.__namespace_operations_api.describe_namespace(**args) + @require_kwargs def delete(self, namespace: str): """ Args: @@ -57,6 +59,7 @@ def delete(self, namespace: str): args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) return self.__namespace_operations_api.delete_namespace(**args) + @require_kwargs def list(self, limit: Optional[int] = None, **kwargs) -> Iterator[ListNamespacesResponse]: """ Args: @@ -82,7 +85,7 @@ def list(self, limit: Optional[int] = None, **kwargs) -> Iterator[ListNamespaces """ done = False while not done: - results = self.list_paginated(limit, **kwargs) + results = self.list_paginated(limit=limit, **kwargs) if results.namespaces is not None and len(results.namespaces) > 0: for namespace in results.namespaces: yield namespace @@ -92,8 +95,9 @@ def list(self, limit: Optional[int] = None, **kwargs) -> Iterator[ListNamespaces else: done = True + @require_kwargs def list_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: """ Args: @@ -115,5 +119,5 @@ def list_paginated( eyJza2lwX3Bhc3QiOiI5OTMiLCJwcmVmaXgiOiI5OSJ9 >>> next_results = index.list_paginated(limit=5, pagination_token=results.pagination.next) """ - args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token) + args = NamespaceRequestFactory.list_namespaces_args(limit=limit, pagination_token=pagination_token, **kwargs) return self.__namespace_operations_api.list_namespaces_operation(**args) \ No newline at end of file diff --git a/pinecone/db_data/resources/sync/namespace_request_factory.py b/pinecone/db_data/resources/sync/namespace_request_factory.py index 6f056fabd..7635e7581 100644 --- a/pinecone/db_data/resources/sync/namespace_request_factory.py +++ b/pinecone/db_data/resources/sync/namespace_request_factory.py @@ -26,6 +26,7 @@ def delete_namespace_args(namespace: str) -> DeleteNamespaceArgs: @staticmethod def list_namespaces_args( - limit: Optional[int] = None, pagination_token: Optional[str] = None + limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> dict[str, Any]: - return parse_non_empty_args([("limit", limit), ("pagination_token", pagination_token)]) \ No newline at end of file + base_args = parse_non_empty_args([("limit", limit), ("pagination_token", pagination_token)]) + return {**base_args, **kwargs} \ No newline at end of file diff --git a/tests/integration/data/test_namespace.py b/tests/integration/data/test_namespace.py index 0c5745b7d..459bfdfd7 100644 --- a/tests/integration/data/test_namespace.py +++ b/tests/integration/data/test_namespace.py @@ -19,7 +19,7 @@ def setup_namespace_data(index, namespace: str, num_vectors: int = 2): def verify_namespace_exists(index, namespace: str) -> bool: """Helper function to verify if a namespace exists""" try: - index.describe_namespace(namespace) + index.describe_namespace(namespace=namespace) return True except Exception: return False @@ -34,7 +34,7 @@ def delete_all_namespaces(index): # Delete each namespace for namespace in namespaces: try: - index.delete_namespace(namespace.name) + index.delete_namespace(namespace=namespace.name) except Exception as e: logger.error(f"Error deleting namespace {namespace.name}: {e}") @@ -55,7 +55,7 @@ def test_describe_namespace(self, idx): try: # Test describe - description = idx.describe_namespace(test_namespace) + description = idx.describe_namespace(namespace=test_namespace) assert isinstance(description, NamespaceDescription) assert description.name == test_namespace finally: @@ -72,7 +72,7 @@ def test_delete_namespace(self, idx): assert verify_namespace_exists(idx, test_namespace) # Delete namespace - idx.delete_namespace(test_namespace) + idx.delete_namespace(namespace=test_namespace) # Wait for namespace to be deleted time.sleep(10) diff --git a/tests/integration/data_asyncio/test_namespace_asyncio.py b/tests/integration/data_asyncio/test_namespace_asyncio.py index b2b86a86b..0a509df04 100644 --- a/tests/integration/data_asyncio/test_namespace_asyncio.py +++ b/tests/integration/data_asyncio/test_namespace_asyncio.py @@ -18,7 +18,7 @@ async def setup_namespace_data(index, namespace: str, num_vectors: int = 2): async def verify_namespace_exists(index, namespace: str) -> bool: """Helper function to verify if a namespace exists""" try: - await index.describe_namespace(namespace) + await index.describe_namespace(namespace=namespace) return True except Exception: return False @@ -33,7 +33,7 @@ async def delete_all_namespaces(index): # Delete each namespace for namespace in namespaces.namespaces: try: - await index.delete_namespace(namespace.name) + await index.delete_namespace(namespace=namespace.name) except Exception as e: logger.error(f"Error deleting namespace {namespace.name}: {e}") @@ -55,7 +55,7 @@ async def test_describe_namespace(self, index_host): try: # Test describe - description = await asyncio_idx.describe_namespace(test_namespace) + description = await asyncio_idx.describe_namespace(namespace=test_namespace) assert isinstance(description, NamespaceDescription) assert description.name == test_namespace finally: @@ -74,7 +74,7 @@ async def test_delete_namespace(self, index_host): assert await verify_namespace_exists(asyncio_idx, test_namespace) # Delete namespace - await asyncio_idx.delete_namespace(test_namespace) + await asyncio_idx.delete_namespace(namespace=test_namespace) # Wait for namespace to be deleted await asyncio.sleep(10) @@ -93,16 +93,18 @@ async def test_list_namespaces(self, index_host): try: # Get all namespaces - namespaces = await asyncio_idx.list_namespaces_paginated() + namespaces = [] + async for ns in asyncio_idx.list_namespaces(): + namespaces.append(ns) # Verify results - assert len(namespaces.namespaces) >= len(test_namespaces) - namespace_names = [ns.name for ns in namespaces.namespaces] + assert len(namespaces) >= len(test_namespaces) + namespace_names = [ns.name for ns in namespaces] for test_ns in test_namespaces: assert test_ns in namespace_names # Verify each namespace has correct structure - for ns in namespaces.namespaces: + for ns in namespaces: assert isinstance(ns, NamespaceDescription) assert hasattr(ns, 'name') assert hasattr(ns, 'vector_count') From 51c29823cb3a69d454f0c4eddc1eaa0d2b80fb3e Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Tue, 17 Jun 2025 10:02:24 -0400 Subject: [PATCH 14/15] add **kwargs to remaining methods --- pinecone/db_data/index.py | 8 ++++---- pinecone/db_data/index_asyncio.py | 10 +++++----- pinecone/db_data/index_asyncio_interface.py | 14 ++++---------- pinecone/db_data/interfaces.py | 8 +++----- .../db_data/resources/asyncio/namespace_asyncio.py | 8 ++++---- pinecone/db_data/resources/sync/namespace.py | 8 ++++---- .../resources/sync/namespace_request_factory.py | 8 ++++---- 7 files changed, 28 insertions(+), 36 deletions(-) diff --git a/pinecone/db_data/index.py b/pinecone/db_data/index.py index 3710e589f..fdee90927 100644 --- a/pinecone/db_data/index.py +++ b/pinecone/db_data/index.py @@ -632,13 +632,13 @@ def cancel_import(self, id: str): @validate_and_convert_errors @require_kwargs - def describe_namespace(self, namespace: str) -> "NamespaceDescription": - return self.namespace.describe(namespace=namespace) + def describe_namespace(self, namespace: str, **kwargs) -> "NamespaceDescription": + return self.namespace.describe(namespace=namespace, **kwargs) @validate_and_convert_errors @require_kwargs - def delete_namespace(self, namespace: str) -> Dict[str, Any]: - return self.namespace.delete(namespace=namespace) + def delete_namespace(self, namespace: str, **kwargs) -> Dict[str, Any]: + return self.namespace.delete(namespace=namespace, **kwargs) @validate_and_convert_errors @require_kwargs diff --git a/pinecone/db_data/index_asyncio.py b/pinecone/db_data/index_asyncio.py index 09ae6eeb4..6bfd53da2 100644 --- a/pinecone/db_data/index_asyncio.py +++ b/pinecone/db_data/index_asyncio.py @@ -671,13 +671,13 @@ async def cancel_import(self, id: str): @validate_and_convert_errors @require_kwargs - async def describe_namespace(self, namespace: str) -> "NamespaceDescription": - return await self.namespace.describe(namespace=namespace) + async def describe_namespace(self, namespace: str, **kwargs) -> "NamespaceDescription": + return await self.namespace.describe(namespace=namespace, **kwargs) @validate_and_convert_errors @require_kwargs - async def delete_namespace(self, namespace: str) -> Dict[str, Any]: - return await self.namespace.delete(namespace=namespace) + async def delete_namespace(self, namespace: str, **kwargs) -> Dict[str, Any]: + return await self.namespace.delete(namespace=namespace, **kwargs) @validate_and_convert_errors @require_kwargs @@ -692,6 +692,6 @@ async def list_namespaces( async def list_namespaces_paginated( self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: - return await self.namespace.list_paginated(limit=limit, pagination_token=pagination_token) + return await self.namespace.list_paginated(limit=limit, pagination_token=pagination_token, **kwargs) IndexAsyncio = _IndexAsyncio diff --git a/pinecone/db_data/index_asyncio_interface.py b/pinecone/db_data/index_asyncio_interface.py index 7128e245a..e057f0a04 100644 --- a/pinecone/db_data/index_asyncio_interface.py +++ b/pinecone/db_data/index_asyncio_interface.py @@ -816,10 +816,7 @@ async def search_records( @abstractmethod @require_kwargs - async def describe_namespace( - self, - namespace: str - ) -> NamespaceDescription: + async def describe_namespace(self, namespace: str, **kwargs) -> NamespaceDescription: """Describe a namespace within an index, showing the vector count within the namespace. Args: @@ -832,10 +829,7 @@ async def describe_namespace( @abstractmethod @require_kwargs - async def delete_namespace( - self, - namespace: str - ) -> Dict[str, Any]: + async def delete_namespace(self, namespace: str, **kwargs) -> Dict[str, Any]: """Delete a namespace from an index. Args: @@ -849,7 +843,7 @@ async def delete_namespace( @abstractmethod @require_kwargs async def list_namespaces( - self, **kwargs + self, limit: Optional[int] = None, **kwargs ) -> AsyncIterator[ListNamespacesResponse]: """List all namespaces in an index. This method automatically handles pagination to return all results. @@ -871,7 +865,7 @@ async def list_namespaces( @abstractmethod @require_kwargs async def list_namespaces_paginated( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None + self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs ) -> ListNamespacesResponse: """List all namespaces in an index with pagination support. The response includes pagination information if there are more results available. diff --git a/pinecone/db_data/interfaces.py b/pinecone/db_data/interfaces.py index 5f11562ad..12c47071c 100644 --- a/pinecone/db_data/interfaces.py +++ b/pinecone/db_data/interfaces.py @@ -796,12 +796,11 @@ def list(self, **kwargs): @abstractmethod @require_kwargs - def describe_namespace(self, namespace: str) -> NamespaceDescription: + def describe_namespace(self, namespace: str, **kwargs) -> NamespaceDescription: """Describe a namespace within an index, showing the vector count within the namespace. Args: namespace (str): The namespace to describe - **kwargs: Additional arguments to pass to the API call Returns: NamespaceDescription: Information about the namespace including vector count @@ -810,12 +809,11 @@ def describe_namespace(self, namespace: str) -> NamespaceDescription: @abstractmethod @require_kwargs - def delete_namespace(self, namespace: str) -> Dict[str, Any]: + def delete_namespace(self, namespace: str, **kwargs) -> Dict[str, Any]: """Delete a namespace from an index. Args: namespace (str): The namespace to delete - **kwargs: Additional arguments to pass to the API call Returns: Dict[str, Any]: Response from the delete operation @@ -825,7 +823,7 @@ def delete_namespace(self, namespace: str) -> Dict[str, Any]: @abstractmethod @require_kwargs def list_namespaces( - self, limit: Optional[int] = None, pagination_token: Optional[str] = None, **kwargs + self, limit: Optional[int] = None, **kwargs ) -> Iterator[ListNamespacesResponse]: """List all namespaces in an index. This method automatically handles pagination to return all results. diff --git a/pinecone/db_data/resources/asyncio/namespace_asyncio.py b/pinecone/db_data/resources/asyncio/namespace_asyncio.py index 10ab78684..5be4e4ae7 100644 --- a/pinecone/db_data/resources/asyncio/namespace_asyncio.py +++ b/pinecone/db_data/resources/asyncio/namespace_asyncio.py @@ -19,7 +19,7 @@ def __init__(self, api_client) -> None: self.__namespace_operations_api = AsyncioNamespaceOperationsApi(api_client) @require_kwargs - async def describe(self, namespace: str) -> NamespaceDescription: + async def describe(self, namespace: str, **kwargs) -> NamespaceDescription: """ Args: namespace (str): The namespace to describe @@ -29,18 +29,18 @@ async def describe(self, namespace: str) -> NamespaceDescription: Describe a namespace within an index, showing the vector count within the namespace. """ - args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace) + args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace, **kwargs) return await self.__namespace_operations_api.describe_namespace(**args) @require_kwargs - async def delete(self, namespace: str): + async def delete(self, namespace: str, **kwargs): """ Args: namespace (str): The namespace to delete Delete a namespace from an index. """ - args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) + args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace, **kwargs) return await self.__namespace_operations_api.delete_namespace(**args) @require_kwargs diff --git a/pinecone/db_data/resources/sync/namespace.py b/pinecone/db_data/resources/sync/namespace.py index 80064b589..944573bcd 100644 --- a/pinecone/db_data/resources/sync/namespace.py +++ b/pinecone/db_data/resources/sync/namespace.py @@ -35,7 +35,7 @@ def __init__( super().__init__() @require_kwargs - def describe(self, namespace: str) -> NamespaceDescription: + def describe(self, namespace: str, **kwargs) -> NamespaceDescription: """ Args: namespace (str): The namespace to describe @@ -45,18 +45,18 @@ def describe(self, namespace: str) -> NamespaceDescription: Describe a namespace within an index, showing the vector count within the namespace. """ - args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace) + args = NamespaceRequestFactory.describe_namespace_args(namespace=namespace, **kwargs) return self.__namespace_operations_api.describe_namespace(**args) @require_kwargs - def delete(self, namespace: str): + def delete(self, namespace: str, **kwargs): """ Args: namespace (str): The namespace to delete Delete a namespace from an index. """ - args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace) + args = NamespaceRequestFactory.delete_namespace_args(namespace=namespace, **kwargs) return self.__namespace_operations_api.delete_namespace(**args) @require_kwargs diff --git a/pinecone/db_data/resources/sync/namespace_request_factory.py b/pinecone/db_data/resources/sync/namespace_request_factory.py index 7635e7581..61855e1cf 100644 --- a/pinecone/db_data/resources/sync/namespace_request_factory.py +++ b/pinecone/db_data/resources/sync/namespace_request_factory.py @@ -13,16 +13,16 @@ class DeleteNamespaceArgs(TypedDict, total=False): class NamespaceRequestFactory: @staticmethod - def describe_namespace_args(namespace: str) -> DescribeNamespaceArgs: + def describe_namespace_args(namespace: str, **kwargs) -> DescribeNamespaceArgs: if not isinstance(namespace, str): raise ValueError('namespace must be string') - return {"namespace": namespace} + return {"namespace": namespace, **kwargs} @staticmethod - def delete_namespace_args(namespace: str) -> DeleteNamespaceArgs: + def delete_namespace_args(namespace: str, **kwargs) -> DeleteNamespaceArgs: if not isinstance(namespace, str): raise ValueError('namespace must be string') - return {"namespace": namespace} + return {"namespace": namespace, **kwargs} @staticmethod def list_namespaces_args( From eed7609e6ce5025e706ddf309e5eee4c46b5f2ce Mon Sep 17 00:00:00 2001 From: rohanshah18 Date: Tue, 17 Jun 2025 10:31:43 -0400 Subject: [PATCH 15/15] use cast for TypedDict --- .../db_data/resources/sync/namespace_request_factory.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pinecone/db_data/resources/sync/namespace_request_factory.py b/pinecone/db_data/resources/sync/namespace_request_factory.py index 61855e1cf..7174276ba 100644 --- a/pinecone/db_data/resources/sync/namespace_request_factory.py +++ b/pinecone/db_data/resources/sync/namespace_request_factory.py @@ -1,4 +1,4 @@ -from typing import Optional, TypedDict, Any +from typing import Optional, TypedDict, Any, cast from pinecone.utils import parse_non_empty_args @@ -16,13 +16,15 @@ class NamespaceRequestFactory: def describe_namespace_args(namespace: str, **kwargs) -> DescribeNamespaceArgs: if not isinstance(namespace, str): raise ValueError('namespace must be string') - return {"namespace": namespace, **kwargs} + base_args = {"namespace": namespace} + return cast(DescribeNamespaceArgs, {**base_args, **kwargs}) @staticmethod def delete_namespace_args(namespace: str, **kwargs) -> DeleteNamespaceArgs: if not isinstance(namespace, str): raise ValueError('namespace must be string') - return {"namespace": namespace, **kwargs} + base_args = {"namespace": namespace} + return cast(DeleteNamespaceArgs, {**base_args, **kwargs}) @staticmethod def list_namespaces_args(