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
414 changes: 189 additions & 225 deletions pinecone/db_control/request_factory.py

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions pinecone/db_control/resources/asyncio/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
from pinecone.db_control.request_factory import PineconeDBControlRequestFactory
from pinecone.core.openapi.db_control import API_VERSION
from pinecone.utils import require_kwargs
from pinecone.db_control.types.configure_index_embed import ConfigureIndexEmbed

logger = logging.getLogger(__name__)
""" :meta private: """
Expand All @@ -43,13 +42,13 @@
MetadataSchemaFieldConfig,
)
from pinecone.core.openapi.db_control.model.read_capacity import ReadCapacity
from pinecone.core.openapi.db_control.model.schema import Schema
from pinecone.core.openapi.db_control.model.read_capacity_on_demand_spec import (
ReadCapacityOnDemandSpec,
)
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


class IndexResourceAsyncio:
Expand Down Expand Up @@ -137,6 +136,8 @@ async def create(
)
else:
# Legacy spec-based creation
# spec is guaranteed to be non-None here due to validation above
assert spec is not None, "spec must be provided when schema is not specified"
req = PineconeDBControlRequestFactory.create_index_request(
name=name,
spec=spec,
Expand Down Expand Up @@ -179,7 +180,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 +227,10 @@ 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
if hasattr(status, "state") and status.state == "InitializationFailed":
raise Exception(f"Index {name} failed to initialize.")
if description.status.ready:
if hasattr(status, "ready") and status.ready:
return description

if timeout is not None and total_wait_time >= timeout:
Expand Down Expand Up @@ -296,7 +298,6 @@ async def configure(
pod_type: (PodType | str) | None = None,
deletion_protection: (DeletionProtection | str) | None = None,
tags: dict[str, str] | None = None,
embed: (ConfigureIndexEmbed | Dict) | None = None,
read_capacity: (
"ReadCapacityDict"
| "ReadCapacity"
Expand All @@ -313,7 +314,6 @@ async def configure(
pod_type=pod_type,
deletion_protection=deletion_protection,
tags=tags,
embed=embed,
read_capacity=read_capacity,
)
await self._index_api.configure_index(name, configure_index_request=req)
16 changes: 8 additions & 8 deletions pinecone/db_control/resources/sync/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
from pinecone.db_control.types import CreateIndexForModelEmbedTypedDict
from pinecone.db_control.request_factory import PineconeDBControlRequestFactory
from pinecone.core.openapi.db_control import API_VERSION
from pinecone.db_control.types.configure_index_embed import ConfigureIndexEmbed

logger = logging.getLogger(__name__)
""" :meta private: """

if TYPE_CHECKING:
from pinecone.config import Config, OpenApiConfiguration
from pinecone.core.openapi.db_control.api.manage_indexes_api import ManageIndexesApi
from pinecone.core.openapi.db_control.model.schema import Schema
from pinecone.db_control.enums import (
Metric,
VectorType,
Expand All @@ -49,7 +49,6 @@
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


class IndexResource(PluginAware):
Expand Down Expand Up @@ -157,6 +156,8 @@ def create(
)
else:
# Legacy spec-based creation
# spec is guaranteed to be non-None here due to validation above
assert spec is not None, "spec must be provided when schema is not specified"
req = PineconeDBControlRequestFactory.create_index_request(
name=name,
spec=spec,
Expand Down Expand Up @@ -199,7 +200,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 +264,12 @@ def __poll_describe_index_until_ready(
total_wait_time = 0
while True:
description = self.describe(name=name)
if description.status.state == "InitializationFailed":
status = description.status
if hasattr(status, "state") and status.state == "InitializationFailed":
raise Exception(
f"Index {name} failed to initialize. The index status is {description.status.state}."
f"Index {name} failed to initialize. The index status is {status.state}."
)
if description.status.ready:
if hasattr(status, "ready") and status.ready:
return description

if timeout is not None and total_wait_time >= timeout:
Expand Down Expand Up @@ -339,7 +341,6 @@ def configure(
pod_type: ("PodType" | str) | None = None,
deletion_protection: ("DeletionProtection" | str) | None = None,
tags: dict[str, str] | None = None,
embed: ("ConfigureIndexEmbed" | Dict) | None = None,
read_capacity: (
"ReadCapacityDict"
| "ReadCapacity"
Expand All @@ -357,7 +358,6 @@ def configure(
pod_type=pod_type,
deletion_protection=deletion_protection,
tags=tags,
embed=embed,
read_capacity=read_capacity,
)
api_instance.configure_index(name, configure_index_request=req)
Expand Down
161 changes: 161 additions & 0 deletions tests/fixtures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
"""Test fixtures for creating test instances of SDK models.

This module provides helper functions for creating test instances with both
old-style and new-style API parameters for backward compatibility.
"""

from typing import Any
from pinecone.core.openapi.db_control.model.index_model import IndexModel as OpenAPIIndexModel
from pinecone.core.openapi.db_control.model.schema import Schema
from pinecone.core.openapi.db_control.model.schema_fields import SchemaFields
from pinecone.core.openapi.db_control.model.serverless_deployment import ServerlessDeployment
from pinecone.core.openapi.db_control.model.pod_deployment import PodDeployment
from pinecone.core.openapi.db_control.model.byoc_deployment import ByocDeployment
from pinecone.core.openapi.db_control.model.index_model_status import IndexModelStatus
from pinecone.db_control.models import IndexModel


def make_index_model(
name: str = "test-index",
dimension: int | None = None,
metric: str | None = None,
spec: dict[str, Any] | None = None,
deployment: ServerlessDeployment | PodDeployment | ByocDeployment | None = None,
schema: Schema | None = None,
host: str = "https://test-index-1234.svc.pinecone.io",
status: IndexModelStatus | None = None,
**kwargs: Any,
) -> IndexModel:
"""Create an IndexModel for testing, accepting both old and new API parameters.

This helper function allows tests to use either the old-style parameters
(spec, dimension, metric) or new-style parameters (deployment, schema) for
backward compatibility.

Args:
name: The index name
dimension: Vector dimension (old-style, converted to schema)
metric: Distance metric (old-style, converted to schema)
spec: Index spec dict (old-style, converted to deployment)
deployment: Deployment configuration (new-style)
schema: Schema configuration (new-style)
host: Index host URL
status: Index status
**kwargs: Additional fields to pass to IndexModel

Returns:
IndexModel wrapper instance

Examples:
Old-style API::

index = make_index_model(
name="my-index",
dimension=1536,
metric="cosine",
spec={"serverless": {"cloud": "aws", "region": "us-east-1"}}
)

New-style API::

index = make_index_model(
name="my-index",
schema=Schema(fields={"_values": SchemaFields(
type="dense_vector",
dimension=1536,
metric="cosine"
)}),
deployment=ServerlessDeployment(
deployment_type="serverless",
cloud="aws",
region="us-east-1"
)
)
"""
# Convert old-style spec to deployment if needed
if deployment is None and spec:
deployment = _spec_to_deployment(spec)
elif deployment is None:
# Default serverless deployment
deployment = ServerlessDeployment(
deployment_type="serverless", cloud="aws", region="us-east-1"
)

# Convert dimension/metric to schema if needed
if schema is None and (dimension or metric):
schema = _create_schema_from_dimension_metric(dimension, metric)
elif schema is None:
# Default empty schema (for FTS-only indexes)
schema = Schema(fields={})

# Create status if needed
if status is None:
status = IndexModelStatus(ready=True, state="Ready")

# Create the OpenAPI model
openapi_model = OpenAPIIndexModel(
name=name, schema=schema, deployment=deployment, host=host, status=status, **kwargs
)

# Wrap in the IndexModel wrapper
return IndexModel(openapi_model)


def _spec_to_deployment(
spec: dict[str, Any],
) -> ServerlessDeployment | PodDeployment | ByocDeployment:
"""Convert old-style spec dict to new-style Deployment.

Args:
spec: Spec dict with serverless, pod, or byoc key

Returns:
Appropriate Deployment instance
"""
if "serverless" in spec:
serverless_spec = spec["serverless"]
return ServerlessDeployment(
deployment_type="serverless",
cloud=serverless_spec.get("cloud", "aws"),
region=serverless_spec.get("region", "us-east-1"),
)
elif "pod" in spec:
pod_spec = spec["pod"]
return PodDeployment(
deployment_type="pod",
environment=pod_spec.get("environment", "us-east-1-aws"),
pod_type=pod_spec.get("pod_type", "p1.x1"),
replicas=pod_spec.get("replicas", 1),
shards=pod_spec.get("shards", 1),
pods=pod_spec.get("pods", 1),
)
elif "byoc" in spec:
byoc_spec = spec["byoc"]
return ByocDeployment(
deployment_type="byoc", environment=byoc_spec.get("environment", "custom-env")
)
else:
raise ValueError("spec must contain 'serverless', 'pod', or 'byoc' key")


def _create_schema_from_dimension_metric(dimension: int | None, metric: str | None) -> Schema:
"""Create a Schema from dimension and metric.

Args:
dimension: Vector dimension
metric: Distance metric

Returns:
Schema with a dense_vector field
"""
if dimension is None:
# No dimension means FTS-only or sparse vectors
# For simplicity, return empty schema
return Schema(fields={})

field_config: dict[str, Any] = {"type": "dense_vector", "dimension": dimension}

if metric:
field_config["metric"] = metric

return Schema(fields={"_values": SchemaFields(**field_config)})
33 changes: 23 additions & 10 deletions tests/unit/db_control/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,19 @@ def test_describe_index(self, mocker):
{
"name": "test-index",
"description": "test-description",
"dimension": 1024,
"metric": "cosine",
"spec": {
"byoc": {
"environment": "test-environment"
"schema": {
"fields": {
"_values": {
"type": "dense_vector",
"dimension": 1024,
"metric": "cosine"
}
}
},
"vector_type": "dense",
"deployment": {
"deployment_type": "byoc",
"environment": "test-environment"
},
"status": {
"ready": true,
"state": "Ready"
Expand All @@ -60,9 +65,10 @@ def test_describe_index(self, mocker):
desc = index_resource.describe(name="test-index")
assert desc.name == "test-index"
assert desc.description == "test-description"
# Test backward compatibility properties
assert desc.dimension == 1024
assert desc.metric == "cosine"
assert desc.spec["byoc"]["environment"] == "test-environment"
assert desc.spec.byoc.environment == "test-environment"
assert desc.vector_type == "dense"
assert desc.status.ready == True
assert desc.deletion_protection == "disabled"
Expand Down Expand Up @@ -104,9 +110,16 @@ def test_create_with_spec_uses_legacy_path(self, mocker):
"""Test that create() with spec uses the legacy request factory method."""
body = """{
"name": "test-index",
"dimension": 1536,
"metric": "cosine",
"spec": {"serverless": {"cloud": "aws", "region": "us-east-1"}},
"schema": {
"fields": {
"_values": {
"type": "dense_vector",
"dimension": 1536,
"metric": "cosine"
}
}
},
"deployment": {"deployment_type": "serverless", "cloud": "aws", "region": "us-east-1"},
"status": {"ready": true, "state": "Ready"},
"host": "test.pinecone.io"
}"""
Expand Down
Loading