# Protocol Specifications

## Introduction

This document explains how protocols work in the Pinecone Python SDK, detailing the 3-layer model (public API, protocols, implementations) and providing comprehensive specifications for all major protocols.

## Protocol System Overview

### What Are Protocols?

Protocols in Python (PEP 544) enable structural subtyping (duck typing) with static type checking. A class satisfies a protocol if it implements the required methods and properties, without explicit inheritance.

```mermaid
graph TB
    subgraph Traditional["Traditional Inheritance"]
        ABC[Abstract Base Class]
        C1[ConcreteClass]
        C1 -->|inherits| ABC
    end

    subgraph Protocol["Protocol-Based"]
        P[Protocol]
        I1[Implementation 1]
        I2[Implementation 2]
        I3[Mock Implementation]

        I1 -.implements.-> P
        I2 -.implements.-> P
        I3 -.implements.-> P
    end

    style Traditional fill:#ffebee
    style Protocol fill:#e8f5e9
```

### Why Protocols?

1. **Flexibility** - No inheritance required
2. **Duck typing** - More Pythonic
3. **Testing** - Easy to create mocks
4. **Type safety** - mypy verifies compliance
5. **Third-party support** - External code can implement protocols

## Three-Layer Protocol Model

### Layer Architecture

```mermaid
graph TB
    subgraph Layer1["Layer 1: Public API"]
        direction TB
        L1A["pinecone/__init__.py"]
        L1B["Exports protocols"]
        L1C["Exports implementations"]
        L1D["User-facing interface"]
    end

    subgraph Layer2["Layer 2: Protocol Definitions"]
        direction TB
        L2A["pinecone/__interface__/"]
        L2B["@runtime_checkable protocols"]
        L2C["Method signatures"]
        L2D["Type annotations"]
        L2E["Documentation"]
    end

    subgraph Layer3["Layer 3: Implementations"]
        direction TB
        L3A["pinecone/_internal/"]
        L3B["Concrete classes"]
        L3C["Business logic"]
        L3D["HTTP communication"]
        L3E["Error handling"]
    end

    User[User Code] --> Layer1
    Layer1 -.exports.-> Layer2
    Layer1 -.exports.-> Layer3
    Layer3 -.implements.-> Layer2

    style Layer1 fill:#e1f5ff
    style Layer2 fill:#fff3e0
    style Layer3 fill:#f3e5f5
```

### Layer Responsibilities

#### Layer 1: Public API

```python
# pinecone/__init__.py

# Import protocols for type hints
from pinecone.__interface__ import (
    IPineconeClient,
    IIndexClient,
    IDataClient,
)

# Import implementations
from pinecone._internal.client import PineconeClient
from pinecone._internal.models import Vector, ScoredVector

# Export everything users need
__all__ = [
    "PineconeClient",      # Concrete implementation
    "IPineconeClient",     # Protocol for type hints
    "IIndexClient",        # Protocol for type hints
    "IDataClient",         # Protocol for type hints
    "Vector",              # Data model
    "ScoredVector",        # Data model
]
```

#### Layer 2: Protocol Definitions

```python
# pinecone/__interface__/client.py

from typing import Protocol, runtime_checkable

@runtime_checkable
class IPineconeClient(Protocol):
    """Protocol defining the Pinecone client interface."""

    def index(self, name: str, host: str | None = None) -> IIndexClient:
        """Get an index client.

        Args:
            name: Index name
            host: Optional host override

        Returns:
            Index client satisfying IIndexClient protocol
        """
        ...
```

#### Layer 3: Implementation

```python
# pinecone/_internal/client.py

from pinecone.__interface__.client import IPineconeClient
from pinecone.__interface__.index import IIndexClient
from pinecone._internal.index import IndexClient

class PineconeClient:
    """Concrete implementation of IPineconeClient protocol."""

    def __init__(self, api_key: str):
        self._api_key = api_key

    def index(self, name: str, host: str | None = None) -> IIndexClient:
        """Implementation of protocol method."""
        return IndexClient(name=name, host=host, api_key=self._api_key)
```

## Protocol Specifications

### Client Protocol Hierarchy

```mermaid
graph TB
    IPC[IPineconeClient]
    IIC[IIndexClient]
    IDC[IDataClient]
    IVC[IVectorClient]

    IPC -->|creates| IIC
    IIC -->|provides| IDC
    IIC -->|provides| IVC

    subgraph Implementations
        PC[PineconeClient]
        IC[IndexClient]
        DC[DataClient]
        VC[VectorClient]
    end

    PC -.implements.-> IPC
    IC -.implements.-> IIC
    DC -.implements.-> IDC
    VC -.implements.-> IVC

    style IPC fill:#fff3e0
    style IIC fill:#fff3e0
    style IDC fill:#fff3e0
    style IVC fill:#fff3e0
    style PC fill:#f3e5f5
    style IC fill:#f3e5f5
    style DC fill:#f3e5f5
    style VC fill:#f3e5f5
```

### IPineconeClient Protocol

The main client protocol for interacting with Pinecone.

```python
from typing import Protocol, runtime_checkable
from pinecone.__interface__.index import IIndexClient

@runtime_checkable
class IPineconeClient(Protocol):
    """Main Pinecone client protocol.

    This protocol defines the top-level operations for managing
    indexes and accessing Pinecone services.
    """

    # Index Management

    def list_indexes(self) -> list[str]:
        """List all available indexes.

        Returns:
            List of index names

        Raises:
            PineconeApiException: If API request fails
        """
        ...

    def create_index(
        self,
        name: str,
        dimension: int,
        metric: str = "cosine",
        spec: dict | None = None,
    ) -> None:
        """Create a new index.

        Args:
            name: Unique index name
            dimension: Vector dimensionality
            metric: Distance metric (cosine, euclidean, dotproduct)
            spec: Index specification (serverless, pod-based)

        Raises:
            ValidationError: If parameters are invalid
            PineconeApiException: If creation fails
        """
        ...

    def delete_index(self, name: str) -> None:
        """Delete an index.

        Args:
            name: Index name to delete

        Raises:
            PineconeApiException: If deletion fails
        """
        ...

    def describe_index(self, name: str) -> dict:
        """Get index details.

        Args:
            name: Index name

        Returns:
            Index metadata and configuration

        Raises:
            PineconeApiException: If request fails
        """
        ...

    # Index Client Access

    def index(self, name: str, host: str | None = None) -> IIndexClient:
        """Get a client for data operations on an index.

        Args:
            name: Index name
            host: Optional host URL override

        Returns:
            Index client satisfying IIndexClient protocol

        Raises:
            ValidationError: If name is invalid
        """
        ...
```

### IIndexClient Protocol

Protocol for index-level operations.

```python
from typing import Protocol, runtime_checkable
from pinecone.__interface__.models import (
    Vector,
    ScoredVector,
    QueryResponse,
    UpsertResponse,
    FetchResponse,
    DeleteResponse,
)

@runtime_checkable
class IIndexClient(Protocol):
    """Index client protocol for data operations."""

    # Vector Operations

    def upsert(
        self,
        vectors: list[Vector],
        namespace: str = "",
    ) -> UpsertResponse:
        """Insert or update vectors.

        Args:
            vectors: List of vectors to upsert
            namespace: Optional namespace

        Returns:
            Response with upserted count

        Raises:
            ValidationError: If vectors are invalid
            PineconeApiException: If request fails
        """
        ...

    def query(
        self,
        vector: list[float] | None = None,
        id: str | None = None,
        top_k: int = 10,
        namespace: str = "",
        filter: dict | None = None,
        include_values: bool = False,
        include_metadata: bool = True,
    ) -> QueryResponse:
        """Query vectors by similarity.

        Args:
            vector: Query vector values
            id: Query by vector ID
            top_k: Number of results to return
            namespace: Optional namespace
            filter: Metadata filter
            include_values: Include vector values in results
            include_metadata: Include metadata in results

        Returns:
            Query results with scored vectors

        Raises:
            ValidationError: If query parameters are invalid
            PineconeApiException: If request fails
        """
        ...

    def fetch(
        self,
        ids: list[str],
        namespace: str = "",
    ) -> FetchResponse:
        """Fetch vectors by ID.

        Args:
            ids: Vector IDs to fetch
            namespace: Optional namespace

        Returns:
            Fetched vectors

        Raises:
            ValidationError: If IDs are invalid
            PineconeApiException: If request fails
        """
        ...

    def delete(
        self,
        ids: list[str] | None = None,
        delete_all: bool = False,
        namespace: str = "",
        filter: dict | None = None,
    ) -> DeleteResponse:
        """Delete vectors.

        Args:
            ids: Vector IDs to delete
            delete_all: Delete all vectors in namespace
            namespace: Optional namespace
            filter: Delete by metadata filter

        Returns:
            Deletion response

        Raises:
            ValidationError: If delete parameters are invalid
            PineconeApiException: If request fails
        """
        ...

    def describe_index_stats(
        self,
        filter: dict | None = None,
    ) -> dict:
        """Get index statistics.

        Args:
            filter: Optional metadata filter

        Returns:
            Index statistics (vector count, dimension, etc.)

        Raises:
            PineconeApiException: If request fails
        """
        ...
```

### Data Model Protocols

```python
from typing import Protocol, runtime_checkable

@runtime_checkable
class IVector(Protocol):
    """Protocol for vector data."""

    @property
    def id(self) -> str:
        """Vector ID."""
        ...

    @property
    def values(self) -> list[float]:
        """Vector values."""
        ...

    @property
    def metadata(self) -> dict | None:
        """Optional metadata."""
        ...

@runtime_checkable
class IScoredVector(Protocol):
    """Protocol for query result vectors."""

    @property
    def id(self) -> str:
        """Vector ID."""
        ...

    @property
    def score(self) -> float:
        """Similarity score."""
        ...

    @property
    def values(self) -> list[float] | None:
        """Optional vector values."""
        ...

    @property
    def metadata(self) -> dict | None:
        """Optional metadata."""
        ...
```

## Protocol Implementation Requirements

### Runtime Checkability

All protocols must be `@runtime_checkable`:

```python
from typing import Protocol, runtime_checkable

@runtime_checkable  # Required!
class IMyProtocol(Protocol):
    def method(self) -> str:
        ...

# Enables runtime checks
obj = get_some_object()
if isinstance(obj, IMyProtocol):
    result = obj.method()
```

### Complete Type Annotations

Every protocol method must have complete type annotations:

```python
# ✅ Good: Complete annotations
@runtime_checkable
class IGoodProtocol(Protocol):
    def method(self, x: int, y: str = "default") -> bool:
        ...

# ❌ Bad: Missing annotations
@runtime_checkable
class IBadProtocol(Protocol):
    def method(self, x, y="default"):  # No types!
        ...
```

### Documentation Requirements

All protocol methods must be documented:

```python
@runtime_checkable
class IDocumentedProtocol(Protocol):
    def method(self, x: int) -> str:
        """Short description of what the method does.

        Args:
            x: Description of parameter

        Returns:
            Description of return value

        Raises:
            ExceptionType: When this exception is raised
        """
        ...
```

## Protocol Verification

### Static Type Checking

```mermaid
graph LR
    subgraph Development["Development Time"]
        CODE[Write Code]
        MYPY[Run mypy]
        CHECK[Verify Protocols]
        FIX[Fix Type Errors]

        CODE --> MYPY --> CHECK
        CHECK -->|Errors| FIX --> CODE
        CHECK -->|Success| DONE[Done]
    end

    style Development fill:#e8f5e9
```

Example mypy verification:

```python
from pinecone import IPineconeClient

def process_client(client: IPineconeClient) -> None:
    # mypy verifies client has required methods
    indexes = client.list_indexes()
    index = client.index("my-index")

# mypy checks implementation satisfies protocol
from pinecone._internal.client import PineconeClient

client = PineconeClient(api_key="...")
process_client(client)  # mypy verifies compatibility
```

### Runtime Checking

```python
from pinecone import IPineconeClient

def safe_process(obj: object) -> None:
    if isinstance(obj, IPineconeClient):
        # Runtime check confirms protocol satisfaction
        obj.list_indexes()
    else:
        raise TypeError("Object does not satisfy IPineconeClient")
```

### Testing Protocol Compliance

```python
import pytest
from pinecone import IPineconeClient
from pinecone._internal.client import PineconeClient

def test_client_implements_protocol():
    """Verify PineconeClient satisfies IPineconeClient protocol."""
    client = PineconeClient(api_key="test-key")

    # Runtime check
    assert isinstance(client, IPineconeClient)

    # Check all required methods exist
    assert hasattr(client, "list_indexes")
    assert hasattr(client, "create_index")
    assert hasattr(client, "delete_index")
    assert hasattr(client, "index")

    # Verify method signatures (mypy does this statically)
    from inspect import signature
    sig = signature(client.index)
    assert "name" in sig.parameters
    assert "host" in sig.parameters
```

## Protocol Design Patterns

### Composition Pattern

```mermaid
graph TB
    subgraph Clients["Client Hierarchy"]
        IPC[IPineconeClient]
        IIC[IIndexClient]
        IDC[IDataClient]
    end

    IPC -->|creates| IIC
    IIC -->|has| IDC

    subgraph Operations["Operations"]
        MGMT[Index Management]
        QUERY[Query Operations]
        CRUD[CRUD Operations]
    end

    IPC --> MGMT
    IIC --> QUERY
    IDC --> CRUD

    style Clients fill:#fff3e0
    style Operations fill:#e8f5e9
```

### Builder Pattern

```python
@runtime_checkable
class IIndexBuilder(Protocol):
    """Protocol for building index configurations."""

    def with_dimension(self, dim: int) -> IIndexBuilder:
        ...

    def with_metric(self, metric: str) -> IIndexBuilder:
        ...

    def with_spec(self, spec: dict) -> IIndexBuilder:
        ...

    def build(self) -> dict:
        """Build final configuration."""
        ...
```

### Strategy Pattern

```python
@runtime_checkable
class IRetryStrategy(Protocol):
    """Protocol for retry strategies."""

    def should_retry(
        self,
        attempt: int,
        exception: Exception,
    ) -> bool:
        ...

    def get_delay(self, attempt: int) -> float:
        ...
```

## Protocol Evolution

### Adding Methods (Breaking Change)

```mermaid
graph LR
    subgraph Before["Before (v1.0)"]
        P1[IClient Protocol]
        M1[method_a]
        M2[method_b]

        P1 --> M1
        P1 --> M2
    end

    subgraph After["After (v2.0)"]
        P2[IClient Protocol]
        M3[method_a]
        M4[method_b]
        M5[method_c NEW]

        P2 --> M3
        P2 --> M4
        P2 --> M5
    end

    Before -.breaking.-> After

    style Before fill:#fff3e0
    style After fill:#ffebee
```

### Adding Optional Parameters (Safe)

```python
# v1.0
@runtime_checkable
class IClient(Protocol):
    def query(self, vector: list[float]) -> QueryResponse:
        ...

# v1.1 - Safe: add optional parameter
@runtime_checkable
class IClient(Protocol):
    def query(
        self,
        vector: list[float],
        top_k: int = 10,  # New optional parameter
    ) -> QueryResponse:
        ...
```

### Deprecation Strategy

```python
@runtime_checkable
class IClient(Protocol):
    def old_method(self) -> str:
        """Deprecated: Use new_method() instead.

        .. deprecated:: 1.5.0
            Use :meth:`new_method` instead.
        """
        ...

    def new_method(self) -> str:
        """Replacement for old_method."""
        ...
```

## Best Practices

### Do's

1. ✅ Use `@runtime_checkable` decorator
2. ✅ Provide complete type annotations
3. ✅ Document all methods with docstrings
4. ✅ Keep protocols focused and cohesive
5. ✅ Use protocols for public interfaces
6. ✅ Test protocol compliance

### Don'ts

1. ❌ Don't add implementation code to protocols
2. ❌ Don't use protocols for internal-only interfaces
3. ❌ Don't create overly complex protocol hierarchies
4. ❌ Don't change protocol methods without major version bump
5. ❌ Don't forget to export protocols in public API

## Related Documentation

- [Overview](./overview.md) - System architecture
- [API Structure](./api-structure.md) - Three-tier API organization
- [Verification](./verification.md) - Testing protocols
- [Type Conventions](../conventions/types.md) - Type system usage
- [Architecture Conventions](../conventions/architecture.md) - Architecture rules
