Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions .github/workflows/on-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,20 @@ concurrency:

jobs:
linting:
# Skip linting for PRs targeting fts branch until SDK compatibility work is complete
if: github.base_ref != 'fts'
uses: './.github/workflows/testing-lint.yaml'

unit-tests:
# Skip unit tests for PRs targeting fts branch until SDK compatibility work is complete
if: github.base_ref != 'fts'
uses: './.github/workflows/testing-unit.yaml'
secrets: inherit
with:
python_versions_json: '["3.10", "3.11", "3.12", "3.13"]'

create-project:
if: github.base_ref != 'fts'
uses: './.github/workflows/project-setup.yaml'
secrets: inherit

integration-tests:
if: always() && (needs.create-project.result == 'success') && github.base_ref != 'fts'
if: always() && (needs.create-project.result == 'success')
uses: './.github/workflows/testing-integration.yaml'
secrets: inherit
needs:
Expand All @@ -62,7 +57,7 @@ jobs:
sparse_index_host: ${{ needs.create-project.outputs.index_host_sparse }}

cleanup-project:
if: always() && github.base_ref != 'fts'
if: always()
needs:
- create-project
- integration-tests
Expand Down
24 changes: 24 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,27 @@ ignore_missing_imports = True

[mypy-aiohttp_retry.*]
ignore_missing_imports = True

[mypy-pandas]
ignore_missing_imports = True
follow_imports = skip

[mypy-pandas.*]
ignore_missing_imports = True
follow_imports = skip

[mypy-google.protobuf]
ignore_missing_imports = True
follow_imports = skip

[mypy-google.protobuf.*]
ignore_missing_imports = True
follow_imports = skip

[mypy-dateutil]
ignore_missing_imports = True
follow_imports = skip

[mypy-dateutil.*]
ignore_missing_imports = True
follow_imports = skip
3 changes: 2 additions & 1 deletion pinecone/admin/eraser/resources/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ def delete(self, name):

def get_state(self, name):
desc = self.pc.db.index.describe(name=name)
return desc["status"]["state"]
status = desc.status
return getattr(status, "state", None)

def list(self):
return self.pc.db.index.list()
6 changes: 3 additions & 3 deletions pinecone/db_control/models/backup_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from pinecone.utils.repr_overrides import custom_serializer

if TYPE_CHECKING:
from pinecone.core.openapi.db_control.model.backup_model_schema import BackupModelSchema
from pinecone.core.openapi.db_control.model.schema import Schema


class BackupModel:
Expand All @@ -21,7 +21,7 @@ def __init__(self, backup: OpenAPIBackupModel):
self._backup = backup

@property
def schema(self) -> "BackupModelSchema" | None:
def schema(self) -> "Schema" | None:
"""Schema for the behavior of Pinecone's internal metadata index.

This property defines which metadata fields are indexed and filterable
Expand All @@ -32,7 +32,7 @@ def schema(self) -> "BackupModelSchema" | None:
The schema is a map of metadata field names to their configuration,
where each field configuration specifies whether the field is filterable.

:type: BackupModelSchema, optional
:type: Schema, optional
:returns: The metadata schema configuration, or None if not set.
"""
return getattr(self._backup, "schema", None)
Expand Down
2 changes: 1 addition & 1 deletion pinecone/db_control/models/index_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def __init__(self, index_list: OpenAPIIndexList):
self.current = 0

def names(self) -> list[str]:
return [i.name for i in self.indexes]
return [str(i.name) for i in self.indexes]

def __getitem__(self, key):
return self.indexes[key]
Expand Down
337 changes: 113 additions & 224 deletions pinecone/db_control/request_factory.py

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions pinecone/db_control/resources/asyncio/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from pinecone.core.openapi.db_control.model.read_capacity_dedicated_spec import (
ReadCapacityDedicatedSpec,
)
from pinecone.core.openapi.db_control.model.backup_model_schema import BackupModelSchema
from pinecone.core.openapi.db_control.model.schema import Schema


class IndexResourceAsyncio:
Expand Down Expand Up @@ -137,6 +137,8 @@ async def create(
)
else:
# Legacy spec-based creation
# spec is guaranteed to be non-None here because we checked above
assert spec is not None
req = PineconeDBControlRequestFactory.create_index_request(
name=name,
spec=spec,
Expand Down Expand Up @@ -179,7 +181,7 @@ async def create_for_model(
| dict[
str, dict[str, Any]
] # Dict with "fields" wrapper: {"fields": {field_name: {...}}, ...}
| "BackupModelSchema" # OpenAPI model instance
| "Schema" # OpenAPI model instance
)
| None = None,
timeout: int | None = None,
Expand Down Expand Up @@ -226,9 +228,12 @@ async def __poll_describe_index_until_ready(
total_wait_time = 0
while True:
description = await self.describe(name=name)
if description.status.state == "InitializationFailed":
status = description.status
state = getattr(status, "state", None)
ready = getattr(status, "ready", False)
if state == "InitializationFailed":
raise Exception(f"Index {name} failed to initialize.")
if description.status.ready:
if ready:
return description

if timeout is not None and total_wait_time >= timeout:
Expand Down
17 changes: 10 additions & 7 deletions pinecone/db_control/resources/sync/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
from pinecone.core.openapi.db_control.model.read_capacity_dedicated_spec import (
ReadCapacityDedicatedSpec,
)
from pinecone.core.openapi.db_control.model.backup_model_schema import BackupModelSchema
from pinecone.core.openapi.db_control.model.schema import Schema


class IndexResource(PluginAware):
Expand Down Expand Up @@ -157,6 +157,8 @@ def create(
)
else:
# Legacy spec-based creation
# spec is guaranteed to be non-None here because we checked above
assert spec is not None
req = PineconeDBControlRequestFactory.create_index_request(
name=name,
spec=spec,
Expand Down Expand Up @@ -199,7 +201,7 @@ def create_for_model(
| dict[
str, dict[str, Any]
] # Dict with "fields" wrapper: {"fields": {field_name: {...}}, ...}
| "BackupModelSchema" # OpenAPI model instance
| "Schema" # OpenAPI model instance
)
| None = None,
timeout: int | None = None,
Expand Down Expand Up @@ -263,11 +265,12 @@ def __poll_describe_index_until_ready(
total_wait_time = 0
while True:
description = self.describe(name=name)
if description.status.state == "InitializationFailed":
raise Exception(
f"Index {name} failed to initialize. The index status is {description.status.state}."
)
if description.status.ready:
status = description.status
state = getattr(status, "state", None)
ready = getattr(status, "ready", False)
if state == "InitializationFailed":
raise Exception(f"Index {name} failed to initialize. The index status is {state}.")
if ready:
return description

if timeout is not None and total_wait_time >= timeout:
Expand Down
68 changes: 43 additions & 25 deletions pinecone/grpc/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
IndexDescription as DescribeIndexStatsResponse,
NamespaceSummary,
NamespaceDescription,
NamespaceDescriptionIndexedFields,
CreateNamespaceRequestSchema,
ListNamespacesResponse,
Pagination as OpenApiPagination,
)
Expand Down Expand Up @@ -482,6 +482,35 @@ def parse_stats_response(
return cast(DescribeIndexStatsResponse, result)


def _parse_proto_schema_to_openapi(proto_schema: Any) -> CreateNamespaceRequestSchema | None:
"""Convert a proto schema to an OpenAPI CreateNamespaceRequestSchema model.

:param proto_schema: A protobuf schema object with a fields attribute.
:returns: OpenAPI schema model or None if no fields present.
"""
from pinecone.core.openapi.db_data.model.create_namespace_request_schema_fields import (
CreateNamespaceRequestSchemaFields,
)

if not proto_schema or not proto_schema.fields:
return None

fields_dict = {}
for field_name, field_config in proto_schema.fields.items():
fields_dict[field_name] = {"type": getattr(field_config, "type", "string")}

if not fields_dict:
return None

return CreateNamespaceRequestSchema(
fields={
k: CreateNamespaceRequestSchemaFields(type=v.get("type", "string"), _check_type=False)
for k, v in fields_dict.items()
},
_check_type=False,
)


def parse_namespace_description(
response: "ProtoNamespaceDescription", initial_metadata: dict[str, str] | None = None
) -> NamespaceDescription:
Expand All @@ -495,18 +524,13 @@ def parse_namespace_description(
name = response.name
record_count = response.record_count

# Extract indexed_fields if present
indexed_fields = None
if response.HasField("indexed_fields") and response.indexed_fields:
# Access indexed_fields.fields directly (RepeatedScalarFieldContainer)
fields_list = list(response.indexed_fields.fields) if response.indexed_fields.fields else []
if fields_list:
indexed_fields = NamespaceDescriptionIndexedFields(
fields=fields_list, _check_type=False
)
# Extract schema if present (replaces indexed_fields in alpha API)
schema = None
if response.HasField("schema") and response.schema:
schema = _parse_proto_schema_to_openapi(response.schema)

namespace_desc = NamespaceDescription(
name=name, record_count=record_count, indexed_fields=indexed_fields, _check_type=False
name=name, record_count=record_count, schema=schema, _check_type=False
)

# Attach _response_info as an attribute (NamespaceDescription is an OpenAPI model)
Expand All @@ -529,23 +553,17 @@ def parse_list_namespaces_response(
# Directly iterate over namespaces
# Pre-allocate namespaces list with known size for better performance
namespaces_proto = response.namespaces
namespaces = [None] * len(namespaces_proto) if namespaces_proto else []
namespaces: list[NamespaceDescription] = (
[None] * len(namespaces_proto) if namespaces_proto else []
) # type: ignore[list-item]
for idx, ns in enumerate(namespaces_proto):
# Extract indexed_fields if present
indexed_fields = None
if ns.HasField("indexed_fields") and ns.indexed_fields:
# Access indexed_fields.fields directly (RepeatedScalarFieldContainer)
fields_list = list(ns.indexed_fields.fields) if ns.indexed_fields.fields else []
if fields_list:
indexed_fields = NamespaceDescriptionIndexedFields(
fields=fields_list, _check_type=False
)
# Extract schema if present (replaces indexed_fields in alpha API)
schema = None
if ns.HasField("schema") and ns.schema:
schema = _parse_proto_schema_to_openapi(ns.schema)

namespaces[idx] = NamespaceDescription(
name=ns.name,
record_count=ns.record_count,
indexed_fields=indexed_fields,
_check_type=False,
name=ns.name, record_count=ns.record_count, schema=schema, _check_type=False
)

# Parse pagination if present
Expand Down
6 changes: 3 additions & 3 deletions pinecone/pinecone.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from pinecone.core.openapi.db_control.model.read_capacity_dedicated_spec import (
ReadCapacityDedicatedSpec,
)
from pinecone.core.openapi.db_control.model.backup_model_schema import BackupModelSchema
from pinecone.core.openapi.db_control.model.schema import Schema
from pinecone.db_control.enums import (
Metric,
VectorType,
Expand Down Expand Up @@ -536,7 +536,7 @@ def create_index_for_model(
| dict[
str, dict[str, Any]
] # Dict with "fields" wrapper: {"fields": {field_name: {...}}, ...}
| "BackupModelSchema" # OpenAPI model instance
| "Schema" # OpenAPI model instance
)
| None = None,
timeout: int | None = None,
Expand All @@ -563,7 +563,7 @@ def create_index_for_model(
:param schema: Optional metadata schema configuration. You can specify ``schema`` to configure which metadata fields are filterable.
The schema can be provided as a dictionary mapping field names to their configurations (e.g., ``{"genre": {"filterable": True}}``)
or as a dictionary with a ``fields`` key (e.g., ``{"fields": {"genre": {"filterable": True}}}``).
:type schema: Optional[Union[dict[str, MetadataSchemaFieldConfig], dict[str, dict[str, Any]], BackupModelSchema]]
:type schema: Optional[Union[dict[str, MetadataSchemaFieldConfig], dict[str, dict[str, Any]], Schema]]
:type timeout: Optional[int]
:param timeout: Specify the number of seconds to wait until index is ready to receive data. If None, wait indefinitely; if >=0, time out after this many seconds;
if -1, return immediately and do not wait.
Expand Down
4 changes: 2 additions & 2 deletions pinecone/pinecone_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
from pinecone.core.openapi.db_control.model.read_capacity_dedicated_spec import (
ReadCapacityDedicatedSpec,
)
from pinecone.core.openapi.db_control.model.backup_model_schema import BackupModelSchema
from pinecone.core.openapi.db_control.model.schema import Schema
from pinecone.core.openapi.db_control.api.manage_indexes_api import AsyncioManageIndexesApi
from pinecone.db_control.index_host_store import IndexHostStore

Expand Down Expand Up @@ -293,7 +293,7 @@ async def create_index_for_model(
| dict[
str, dict[str, Any]
] # Dict with "fields" wrapper: {"fields": {field_name: {...}}, ...}
| "BackupModelSchema" # OpenAPI model instance
| "Schema" # OpenAPI model instance
)
| None = None,
timeout: int | None = None,
Expand Down
6 changes: 3 additions & 3 deletions pinecone/pinecone_interface_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
from pinecone.core.openapi.db_control.model.read_capacity_dedicated_spec import (
ReadCapacityDedicatedSpec,
)
from pinecone.core.openapi.db_control.model.backup_model_schema import BackupModelSchema
from pinecone.core.openapi.db_control.model.schema import Schema


class PineconeAsyncioDBControlInterface(ABC):
Expand Down Expand Up @@ -428,7 +428,7 @@ async def create_index_for_model(
| dict[
str, dict[str, Any]
] # Dict with "fields" wrapper: {"fields": {field_name: {...}}, ...}
| "BackupModelSchema" # OpenAPI model instance
| "Schema" # OpenAPI model instance
)
| None = None,
timeout: int | None = None,
Expand All @@ -454,7 +454,7 @@ async def create_index_for_model(
:param schema: Optional metadata schema configuration. You can specify ``schema`` to configure which metadata fields are filterable.
The schema can be provided as a dictionary mapping field names to their configurations (e.g., ``{"genre": {"filterable": True}}``)
or as a dictionary with a ``fields`` key (e.g., ``{"fields": {"genre": {"filterable": True}}}``).
:type schema: Optional[Union[dict[str, MetadataSchemaFieldConfig], dict[str, dict[str, Any]], BackupModelSchema]]
:type schema: Optional[Union[dict[str, MetadataSchemaFieldConfig], dict[str, dict[str, Any]], Schema]]
:type timeout: Optional[int]
:param timeout: Specify the number of seconds to wait until index is ready to receive data. If None, wait indefinitely; if >=0, time out after this many seconds;
if -1, return immediately and do not wait.
Expand Down
Loading
Loading