-
Notifications
You must be signed in to change notification settings - Fork 115
feat: add Protocol definitions for adapter layer #604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jhamon
merged 2 commits into
main
from
jhamon/sdk-275-phase-2b-protocol-definitions-for-adapter-layer
Feb 3, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| """Protocol definitions for the adapter layer. | ||
|
|
||
| This module defines formal Protocol interfaces that specify the contract between | ||
| generated OpenAPI models and SDK adapter code. These protocols make it explicit | ||
| what properties and methods the SDK code depends on from the OpenAPI models, | ||
| enabling: | ||
|
|
||
| - Type safety with static type checking (mypy) | ||
| - Clear documentation of adapter dependencies | ||
| - Flexibility to change OpenAPI model implementations | ||
| - Better testability through protocol-based mocking | ||
|
|
||
| Each protocol corresponds to an OpenAPI model type that adapters consume. The | ||
| protocols define only the minimal interface required by adapter functions, | ||
| isolating SDK code from the full complexity of generated models. | ||
|
|
||
| Usage: | ||
| >>> from pinecone.adapters.protocols import QueryResponseAdapter | ||
| >>> def adapt_query(response: QueryResponseAdapter) -> QueryResponse: | ||
| ... return QueryResponse(matches=response.matches) | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import TYPE_CHECKING, Any, Protocol | ||
|
|
||
| if TYPE_CHECKING: | ||
| from pinecone.core.openapi.db_data.models import ScoredVector, Usage | ||
| from pinecone.core.openapi.db_control.model.index_model_status import IndexModelStatus | ||
|
|
||
|
|
||
| class QueryResponseAdapter(Protocol): | ||
| """Protocol for OpenAPI QueryResponse objects used in adapters. | ||
|
|
||
| This protocol defines the minimal interface that SDK code depends on when | ||
| adapting an OpenAPI QueryResponse to the SDK QueryResponse dataclass. | ||
|
|
||
| Attributes: | ||
| matches: List of scored vectors returned by the query. | ||
| namespace: The namespace that was queried. | ||
| usage: Optional usage statistics for the query operation. | ||
| _data_store: Internal data storage (for accessing raw response data). | ||
| _response_info: Response metadata including headers. | ||
| """ | ||
|
|
||
| matches: list[ScoredVector] | ||
| namespace: str | None | ||
| usage: Usage | None | ||
| _data_store: dict[str, Any] | ||
| _response_info: Any | ||
|
|
||
|
|
||
| class UpsertResponseAdapter(Protocol): | ||
| """Protocol for OpenAPI UpsertResponse objects used in adapters. | ||
|
|
||
| This protocol defines the minimal interface that SDK code depends on when | ||
| adapting an OpenAPI UpsertResponse to the SDK UpsertResponse dataclass. | ||
|
|
||
| Attributes: | ||
| upserted_count: Number of vectors that were successfully upserted. | ||
| _response_info: Response metadata including headers. | ||
| """ | ||
|
|
||
| upserted_count: int | ||
| _response_info: Any | ||
|
|
||
|
|
||
| class FetchResponseAdapter(Protocol): | ||
| """Protocol for OpenAPI FetchResponse objects used in adapters. | ||
|
|
||
| This protocol defines the minimal interface that SDK code depends on when | ||
| adapting an OpenAPI FetchResponse to the SDK FetchResponse dataclass. | ||
|
|
||
| Attributes: | ||
| namespace: The namespace from which vectors were fetched (optional). | ||
| vectors: Dictionary mapping vector IDs to Vector objects. | ||
| usage: Optional usage statistics for the fetch operation. | ||
| _response_info: Response metadata including headers. | ||
| """ | ||
|
|
||
| namespace: str | None | ||
| vectors: dict[str, Any] | ||
| usage: Usage | None | ||
| _response_info: Any | ||
|
|
||
|
|
||
| class IndexModelAdapter(Protocol): | ||
| """Protocol for OpenAPI IndexModel objects used in adapters. | ||
|
|
||
| This protocol defines the minimal interface that SDK code depends on when | ||
| working with OpenAPI IndexModel objects. The IndexModel wrapper class | ||
| provides additional functionality on top of this protocol. | ||
|
|
||
| Attributes: | ||
| name: The name of the index. | ||
| dimension: The dimensionality of vectors in the index. | ||
| metric: The distance metric used for similarity search. | ||
| host: The host URL for the index. | ||
| spec: The index specification (serverless, pod, or BYOC). | ||
| status: The current status of the index. | ||
| _data_store: Internal data storage (for accessing raw response data). | ||
| _configuration: OpenAPI configuration object. | ||
| _path_to_item: Path to this item in the response tree. | ||
| """ | ||
|
|
||
| name: str | ||
| dimension: int | ||
| metric: str | ||
| host: str | ||
| spec: Any | ||
| status: IndexModelStatus | ||
| _data_store: dict[str, Any] | ||
| _configuration: Any | ||
| _path_to_item: tuple[str, ...] | list[str] | ||
|
|
||
| def to_dict(self) -> dict[str, Any]: | ||
| """Convert the index model to a dictionary representation. | ||
|
|
||
| Returns: | ||
| Dictionary representation of the index model. | ||
| """ | ||
| ... | ||
|
|
||
|
|
||
| class IndexStatusAdapter(Protocol): | ||
| """Protocol for IndexModelStatus objects used in adapters. | ||
|
|
||
| This protocol defines the minimal interface that SDK code depends on when | ||
| working with index status information. | ||
|
|
||
| Attributes: | ||
| ready: Whether the index is ready to serve requests. | ||
| state: The current state of the index (e.g., 'Ready', 'Initializing'). | ||
| """ | ||
|
|
||
| ready: bool | ||
| state: str | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| """Unit tests for adapter protocol compliance. | ||
|
|
||
| These tests verify that the actual OpenAPI models satisfy the protocol | ||
| interfaces defined in pinecone.adapters.protocols. This ensures that the | ||
| adapter layer's contracts are maintained even as the OpenAPI models change. | ||
| """ | ||
|
|
||
| from pinecone.adapters.protocols import ( | ||
| QueryResponseAdapter, | ||
| UpsertResponseAdapter, | ||
| FetchResponseAdapter, | ||
| IndexModelAdapter, | ||
| IndexStatusAdapter, | ||
| ) | ||
| from tests.fixtures import ( | ||
| make_openapi_query_response, | ||
| make_openapi_upsert_response, | ||
| make_openapi_fetch_response, | ||
| ) | ||
|
|
||
|
|
||
| class TestQueryResponseProtocolCompliance: | ||
| """Tests that OpenAPI QueryResponse satisfies QueryResponseAdapter protocol.""" | ||
|
|
||
| def test_has_matches_attribute(self): | ||
| """Test that QueryResponse has matches attribute.""" | ||
| response = make_openapi_query_response(matches=[]) | ||
| # This satisfies the protocol check | ||
| _protocol_check: QueryResponseAdapter = response | ||
| assert hasattr(response, "matches") | ||
|
|
||
| def test_has_namespace_attribute(self): | ||
| """Test that QueryResponse has namespace attribute.""" | ||
| response = make_openapi_query_response(matches=[], namespace="test") | ||
| _protocol_check: QueryResponseAdapter = response | ||
| assert hasattr(response, "namespace") | ||
|
|
||
| def test_has_usage_attribute(self): | ||
| """Test that QueryResponse has usage attribute.""" | ||
| response = make_openapi_query_response(matches=[]) | ||
| _protocol_check: QueryResponseAdapter = response | ||
| assert hasattr(response, "usage") | ||
|
|
||
| def test_has_data_store_attribute(self): | ||
| """Test that QueryResponse has _data_store attribute.""" | ||
| response = make_openapi_query_response(matches=[]) | ||
| _protocol_check: QueryResponseAdapter = response | ||
| assert hasattr(response, "_data_store") | ||
|
|
||
| def test_has_response_info_attribute(self): | ||
| """Test that QueryResponse has _response_info attribute.""" | ||
| response = make_openapi_query_response(matches=[]) | ||
| _protocol_check: QueryResponseAdapter = response | ||
| assert hasattr(response, "_response_info") | ||
|
|
||
|
|
||
| class TestUpsertResponseProtocolCompliance: | ||
| """Tests that OpenAPI UpsertResponse satisfies UpsertResponseAdapter protocol.""" | ||
|
|
||
| def test_has_upserted_count_attribute(self): | ||
| """Test that UpsertResponse has upserted_count attribute.""" | ||
| response = make_openapi_upsert_response(upserted_count=10) | ||
| _protocol_check: UpsertResponseAdapter = response | ||
| assert hasattr(response, "upserted_count") | ||
| assert response.upserted_count == 10 | ||
|
|
||
| def test_has_response_info_attribute(self): | ||
| """Test that UpsertResponse has _response_info attribute.""" | ||
| response = make_openapi_upsert_response(upserted_count=10) | ||
| _protocol_check: UpsertResponseAdapter = response | ||
| assert hasattr(response, "_response_info") | ||
|
|
||
|
|
||
| class TestFetchResponseProtocolCompliance: | ||
| """Tests that OpenAPI FetchResponse satisfies FetchResponseAdapter protocol.""" | ||
|
|
||
| def test_has_namespace_attribute(self): | ||
| """Test that FetchResponse has namespace attribute.""" | ||
| response = make_openapi_fetch_response(vectors={}, namespace="test") | ||
| _protocol_check: FetchResponseAdapter = response | ||
| assert hasattr(response, "namespace") | ||
| assert response.namespace == "test" | ||
|
|
||
| def test_has_vectors_attribute(self): | ||
| """Test that FetchResponse has vectors attribute.""" | ||
| response = make_openapi_fetch_response(vectors={}) | ||
| _protocol_check: FetchResponseAdapter = response | ||
| assert hasattr(response, "vectors") | ||
|
|
||
| def test_has_usage_attribute(self): | ||
| """Test that FetchResponse has usage attribute.""" | ||
| response = make_openapi_fetch_response(vectors={}) | ||
| _protocol_check: FetchResponseAdapter = response | ||
| assert hasattr(response, "usage") | ||
|
|
||
| def test_has_response_info_attribute(self): | ||
| """Test that FetchResponse has _response_info attribute.""" | ||
| response = make_openapi_fetch_response(vectors={}) | ||
| _protocol_check: FetchResponseAdapter = response | ||
| assert hasattr(response, "_response_info") | ||
|
|
||
|
|
||
| class TestIndexModelProtocolCompliance: | ||
| """Tests that OpenAPI IndexModel satisfies IndexModelAdapter protocol.""" | ||
|
|
||
| def test_openapi_index_model_has_required_attributes(self): | ||
| """Test that OpenAPI IndexModel has all required protocol attributes.""" | ||
| from pinecone.core.openapi.db_control.model.index_model import ( | ||
| IndexModel as OpenAPIIndexModel, | ||
| ) | ||
| from pinecone.core.openapi.db_control.model.index_model_status import IndexModelStatus | ||
|
|
||
| # Create a minimal OpenAPI IndexModel | ||
| index = OpenAPIIndexModel._new_from_openapi_data( | ||
| name="test-index", | ||
| dimension=128, | ||
| metric="cosine", | ||
| host="test-host.pinecone.io", | ||
| spec={"serverless": {"cloud": "aws", "region": "us-east-1"}}, | ||
| status=IndexModelStatus._new_from_openapi_data(ready=True, state="Ready"), | ||
| ) | ||
|
|
||
| # This satisfies the protocol check | ||
| _protocol_check: IndexModelAdapter = index | ||
|
|
||
| # Verify all required attributes exist | ||
| assert hasattr(index, "name") | ||
| assert hasattr(index, "dimension") | ||
| assert hasattr(index, "metric") | ||
| assert hasattr(index, "host") | ||
| assert hasattr(index, "spec") | ||
| assert hasattr(index, "status") | ||
| assert hasattr(index, "_data_store") | ||
| assert hasattr(index, "_configuration") | ||
| assert hasattr(index, "_path_to_item") | ||
| assert hasattr(index, "to_dict") | ||
| assert callable(index.to_dict) | ||
|
|
||
|
|
||
| class TestIndexStatusProtocolCompliance: | ||
| """Tests that IndexModelStatus satisfies IndexStatusAdapter protocol.""" | ||
|
|
||
| def test_openapi_index_status_has_required_attributes(self): | ||
| """Test that IndexModelStatus has all required protocol attributes.""" | ||
| from pinecone.core.openapi.db_control.model.index_model_status import IndexModelStatus | ||
|
|
||
| status = IndexModelStatus._new_from_openapi_data(ready=True, state="Ready") | ||
|
|
||
| # This satisfies the protocol check | ||
| _protocol_check: IndexStatusAdapter = status | ||
|
|
||
| # Verify all required attributes exist | ||
| assert hasattr(status, "ready") | ||
| assert hasattr(status, "state") | ||
| assert status.ready is True | ||
| assert status.state == "Ready" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Protocol incorrectly marks
vectorsas required when API allows NoneMedium Severity
The
FetchResponseAdapterprotocol definesvectors: dict[str, Any]as a required attribute, but the OpenAPI spec marksvectorsas[optional]. If the API returns a response withoutvectors(or withnull), the adapter code atopenapi_response.vectors.items()will crash with anAttributeError. The protocol type should bedict[str, Any] | Noneto match the actual API contract and prompt defensive handling.