Skip to content

Commit 8f9f55d

Browse files
authored
Migrate to Python 3.10+ Type Syntax (#548)
# Migrate to Python 3.10+ Type Syntax ## Summary This PR modernizes the Pinecone Python SDK's type annotations by migrating from legacy `typing` module syntax to Python 3.10+ built-in type syntax. All `Union[X, Y]` usages are replaced with `X | Y`, all `Optional[X]` usages are replaced with `X | None`, and deprecated typing aliases (`Dict`, `Tuple`) are replaced with built-in types (`dict`, `tuple`). ## Problem The SDK was using legacy type annotation syntax that has been superseded by cleaner, more readable Python 3.10+ syntax: - `Union[X, Y]` is verbose and less readable than `X | Y` - `Optional[X]` is redundant when `X | None` is more explicit - `Dict` and `Tuple` from `typing` are deprecated in favor of built-in `dict` and `tuple` (PEP 585) Since the SDK already requires Python 3.10+, we can take advantage of these modern syntax improvements. ## Solution Migrated all type annotations throughout the codebase to use Python 3.10+ syntax: - Replaced `Union[X, Y]` with `X | Y` syntax - Replaced `Optional[X]` with `X | None` syntax - Replaced `Dict` with `dict` and `Tuple` with `tuple` in non-generated code - Added `from __future__ import annotations` where needed for forward references - Used `List` from `typing` only where necessary to avoid conflicts with methods named `list` ## User-Facing Impact ### Benefits - **Cleaner, More Readable Code**: Modern type syntax is more concise and easier to read - **Better IDE Support**: IDEs better understand the modern syntax and provide improved autocomplete - **Future-Proof**: Aligns with Python's direction and best practices for Python 3.10+ - **No Breaking Changes**: All changes are purely syntactic - runtime behavior is unchanged ### Breaking Changes **None** - This is a purely syntactic change. All existing code continues to work without modification. ### Migration Guide No migration required for users. The changes are internal to the SDK and transparent to users. ## Example Usage The changes are internal, but here's how the improved type annotations look: ### Before ```python from typing import Union, Optional, Dict, List def search( query: Union[str, Dict[str, Any]], top_k: int, filter: Optional[Dict[str, Any]] = None, namespace: Optional[str] = None ) -> Dict[str, List[ScoredVector]]: ... ``` ### After ```python from typing import List # Only needed where 'list' method conflicts def search( query: str | dict[str, Any], top_k: int, filter: dict[str, Any] | None = None, namespace: str | None = None ) -> dict[str, List[ScoredVector]]: ... ``` ## Technical Details ### Type Alias Handling For type aliases that reference forward-declared types, we use `TypeAlias` with proper `TYPE_CHECKING` guards to ensure mypy can resolve types correctly while maintaining runtime compatibility. ### Naming Conflicts In classes with methods named `list`, we continue to use `List` from `typing` to avoid shadowing the built-in type. This affects: - `ApiKeyResource.list()` method - `IndexAsyncioInterface.list()` method - `_IndexAsyncio.list()` method - `GRPCIndex.list()` method ### Generated Code Generated files in `pinecone/core/` are not modified, as they are automatically generated from OpenAPI specifications. These will be updated when the code generation templates are updated in a future PR. ## Testing - All existing tests pass (414 unit tests, 4 skipped) - Mypy type checking passes with no errors (353 source files checked) - All files compile successfully ## Compatibility - **Python Version**: Requires Python 3.10+ (already a requirement) - **Backward Compatibility**: Fully backward compatible - no API changes - **Type Checkers**: Compatible with mypy, pyright, and other modern type checkers
1 parent 324c963 commit 8f9f55d

File tree

109 files changed

+1505
-1580
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+1505
-1580
lines changed

pinecone/admin/admin.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from pinecone.core.openapi.oauth import API_VERSION
44
from pinecone.core.openapi.oauth.apis import OAuthApi
55
from pinecone.core.openapi.oauth.models import TokenRequest
6-
from typing import Optional, Dict
76
from pinecone.utils import get_user_agent
87
import os
98
from copy import deepcopy
@@ -34,14 +33,14 @@ class Admin:
3433
:param additional_headers: Additional headers to use for the Pinecone API. This is a
3534
dictionary of key-value pairs. This is primarily used for internal testing
3635
purposes.
37-
:type additional_headers: Optional[Dict[str, str]]
36+
:type additional_headers: Optional[dict[str, str]]
3837
"""
3938

4039
def __init__(
4140
self,
42-
client_id: Optional[str] = None,
43-
client_secret: Optional[str] = None,
44-
additional_headers: Optional[Dict[str, str]] = None,
41+
client_id: str | None = None,
42+
client_secret: str | None = None,
43+
additional_headers: dict[str, str] | None = None,
4544
):
4645
"""
4746
Initialize the ``Admin`` class.
@@ -58,7 +57,7 @@ def __init__(
5857
:param additional_headers: Additional headers to use for the Pinecone API. This is a
5958
dictionary of key-value pairs. This is primarily used for internal testing
6059
purposes.
61-
:type additional_headers: Optional[Dict[str, str]]
60+
:type additional_headers: Optional[dict[str, str]]
6261
"""
6362

6463
if client_id is not None:
@@ -112,7 +111,7 @@ def __init__(
112111
self._child_api_client.user_agent = get_user_agent(Config())
113112

114113
# Lazily initialize resources
115-
from typing import TYPE_CHECKING, Optional
114+
from typing import TYPE_CHECKING
116115

117116
if TYPE_CHECKING:
118117
from pinecone.admin.resources import (
@@ -121,9 +120,9 @@ def __init__(
121120
OrganizationResource,
122121
)
123122

124-
self._project: Optional[ProjectResource] = None
125-
self._api_key: Optional[ApiKeyResource] = None
126-
self._organization: Optional[OrganizationResource] = None
123+
self._project: ProjectResource | None = None
124+
self._api_key: ApiKeyResource | None = None
125+
self._organization: OrganizationResource | None = None
127126
else:
128127
self._project = None # type: ignore[assignment]
129128
self._api_key = None # type: ignore[assignment]

pinecone/admin/resources/api_key.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import Optional, List
1+
from __future__ import annotations
2+
3+
from typing import List
24
from pinecone.openapi_support import ApiClient
35
from pinecone.core.openapi.admin.apis import APIKeysApi
46
from pinecone.utils import require_kwargs, parse_non_empty_args
@@ -151,8 +153,8 @@ def create(
151153
self,
152154
project_id: str,
153155
name: str,
154-
description: Optional[str] = None,
155-
roles: Optional[List[str]] = None,
156+
description: str | None = None,
157+
roles: List[str] | None = None,
156158
):
157159
"""
158160
Create an API key for a project.
@@ -169,7 +171,7 @@ def create(
169171
:param roles: The roles of the API key. Available roles include:
170172
``ProjectEditor``, ``ProjectViewer``, ``ControlPlaneEditor``,
171173
``ControlPlaneViewer``, ``DataPlaneEditor``, ``DataPlaneViewer``
172-
:type roles: Optional[List[str]]
174+
:type roles: Optional[list[str]]
173175
:return: The created API key object and value.
174176
:rtype: {"key": APIKey, "value": str}
175177
@@ -210,9 +212,7 @@ def create(
210212
)
211213

212214
@require_kwargs
213-
def update(
214-
self, api_key_id: str, name: Optional[str] = None, roles: Optional[List[str]] = None
215-
):
215+
def update(self, api_key_id: str, name: str | None = None, roles: List[str] | None = None):
216216
"""
217217
Update an API key.
218218
@@ -226,7 +226,7 @@ def update(
226226
``ControlPlaneViewer``, ``DataPlaneEditor``, ``DataPlaneViewer``.
227227
Existing roles will be removed if not included. If this field is omitted,
228228
the roles will not be updated.
229-
:type roles: Optional[List[str]]
229+
:type roles: Optional[list[str]]
230230
:return: The updated API key.
231231
:rtype: APIKey
232232

pinecone/admin/resources/organization.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Optional
21
from pinecone.openapi_support import ApiClient
32
from pinecone.core.openapi.admin.apis import OrganizationsApi
43
from pinecone.utils import require_kwargs, parse_non_empty_args
@@ -155,7 +154,7 @@ def describe(self, organization_id: str):
155154
return self.fetch(organization_id=organization_id)
156155

157156
@require_kwargs
158-
def update(self, organization_id: str, name: Optional[str] = None):
157+
def update(self, organization_id: str, name: str | None = None):
159158
"""
160159
Update an organization.
161160

pinecone/admin/resources/project.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Optional
21
from pinecone.exceptions import NotFoundException, PineconeException
32
from pinecone.openapi_support import ApiClient
43
from pinecone.core.openapi.admin.apis import ProjectsApi
@@ -79,7 +78,7 @@ def list(self):
7978
return self._projects_api.list_projects()
8079

8180
@require_kwargs
82-
def fetch(self, project_id: Optional[str] = None, name: Optional[str] = None):
81+
def fetch(self, project_id: str | None = None, name: str | None = None):
8382
"""
8483
Fetch a project by project_id or name.
8584
@@ -152,7 +151,7 @@ def fetch(self, project_id: Optional[str] = None, name: Optional[str] = None):
152151
return projects[0]
153152

154153
@require_kwargs
155-
def get(self, project_id: Optional[str] = None, name: Optional[str] = None):
154+
def get(self, project_id: str | None = None, name: str | None = None):
156155
"""Alias for :func:`fetch`
157156
158157
Examples
@@ -179,7 +178,7 @@ def get(self, project_id: Optional[str] = None, name: Optional[str] = None):
179178
return self.fetch(project_id=project_id, name=name)
180179

181180
@require_kwargs
182-
def describe(self, project_id: Optional[str] = None, name: Optional[str] = None):
181+
def describe(self, project_id: str | None = None, name: str | None = None):
183182
"""Alias for :func:`fetch`
184183
185184
Examples
@@ -206,7 +205,7 @@ def describe(self, project_id: Optional[str] = None, name: Optional[str] = None)
206205
return self.fetch(project_id=project_id, name=name)
207206

208207
@require_kwargs
209-
def exists(self, project_id: Optional[str] = None, name: Optional[str] = None):
208+
def exists(self, project_id: str | None = None, name: str | None = None):
210209
"""
211210
Check if a project exists by project_id or name.
212211
@@ -272,10 +271,7 @@ def exists(self, project_id: Optional[str] = None, name: Optional[str] = None):
272271

273272
@require_kwargs
274273
def create(
275-
self,
276-
name: str,
277-
max_pods: Optional[int] = None,
278-
force_encryption_with_cmek: Optional[bool] = None,
274+
self, name: str, max_pods: int | None = None, force_encryption_with_cmek: bool | None = None
279275
):
280276
"""
281277
Create a project.
@@ -328,9 +324,9 @@ def create(
328324
def update(
329325
self,
330326
project_id: str,
331-
name: Optional[str] = None,
332-
max_pods: Optional[int] = None,
333-
force_encryption_with_cmek: Optional[bool] = None,
327+
name: str | None = None,
328+
max_pods: int | None = None,
329+
force_encryption_with_cmek: bool | None = None,
334330
):
335331
"""
336332
Update a project.

pinecone/config/config.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from typing import NamedTuple, Optional, Dict, TYPE_CHECKING
1+
from __future__ import annotations
2+
3+
from typing import NamedTuple, TYPE_CHECKING
24
import os
35

46
from pinecone.exceptions import PineconeConfigurationError
@@ -9,7 +11,7 @@
911

1012

1113
# Duplicated this util to help resolve circular imports
12-
def normalize_host(host: Optional[str]) -> str:
14+
def normalize_host(host: str | None) -> str:
1315
if host is None:
1416
return ""
1517
if host.startswith("https://"):
@@ -22,12 +24,12 @@ def normalize_host(host: Optional[str]) -> str:
2224
class Config(NamedTuple):
2325
api_key: str = ""
2426
host: str = ""
25-
proxy_url: Optional[str] = None
26-
proxy_headers: Optional[Dict[str, str]] = None
27-
ssl_ca_certs: Optional[str] = None
28-
ssl_verify: Optional[bool] = None
29-
additional_headers: Optional[Dict[str, str]] = {}
30-
source_tag: Optional[str] = None
27+
proxy_url: str | None = None
28+
proxy_headers: dict[str, str] | None = None
29+
ssl_ca_certs: str | None = None
30+
ssl_verify: bool | None = None
31+
additional_headers: dict[str, str] | None = {}
32+
source_tag: str | None = None
3133

3234

3335
class ConfigBuilder:
@@ -49,13 +51,13 @@ class ConfigBuilder:
4951

5052
@staticmethod
5153
def build(
52-
api_key: Optional[str] = None,
53-
host: Optional[str] = None,
54-
proxy_url: Optional[str] = None,
55-
proxy_headers: Optional[Dict[str, str]] = None,
56-
ssl_ca_certs: Optional[str] = None,
57-
ssl_verify: Optional[bool] = None,
58-
additional_headers: Optional[Dict[str, str]] = {},
54+
api_key: str | None = None,
55+
host: str | None = None,
56+
proxy_url: str | None = None,
57+
proxy_headers: dict[str, str] | None = None,
58+
ssl_ca_certs: str | None = None,
59+
ssl_verify: bool | None = None,
60+
additional_headers: dict[str, str] | None = {},
5961
**kwargs,
6062
) -> Config:
6163
api_key = api_key or kwargs.pop("api_key", None) or os.getenv("PINECONE_API_KEY")
@@ -83,7 +85,7 @@ def build(
8385

8486
@staticmethod
8587
def build_openapi_config(
86-
config: Config, openapi_config: Optional["OpenApiConfiguration"] = None, **kwargs
88+
config: Config, openapi_config: "OpenApiConfiguration" | None = None, **kwargs
8789
) -> "OpenApiConfiguration":
8890
if openapi_config:
8991
openapi_config = OpenApiConfigFactory.copy(

pinecone/config/openapi_config_factory.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import sys
2-
from typing import List, Optional, Tuple
32

43
import certifi
54
import socket
@@ -14,7 +13,7 @@
1413

1514
class OpenApiConfigFactory:
1615
@classmethod
17-
def build(cls, api_key: str, host: Optional[str] = None, **kwargs):
16+
def build(cls, api_key: str, host: str | None = None, **kwargs):
1817
openapi_config = OpenApiConfiguration()
1918
openapi_config.api_key = {"ApiKeyAuth": api_key}
2019
openapi_config.host = host
@@ -56,7 +55,7 @@ def _get_socket_options(
5655
keep_alive_idle_sec: int = TCP_KEEPIDLE,
5756
keep_alive_interval_sec: int = TCP_KEEPINTVL,
5857
keep_alive_tries: int = TCP_KEEPCNT,
59-
) -> List[Tuple[int, int, int]]:
58+
) -> list[tuple[int, int, int]]:
6059
"""
6160
Returns the socket options to pass to OpenAPI's Rest client
6261
Args:

pinecone/config/openapi_configuration.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import multiprocessing
44

55
from pinecone.exceptions import PineconeApiValueError
6-
from typing import TypedDict, Optional
6+
from typing import TypedDict
77

88

99
class HostSetting(TypedDict):
@@ -297,7 +297,7 @@ def debug(self, value: bool) -> None:
297297
:param value: The debug status, True or False.
298298
:type: bool
299299
"""
300-
previous_debug: Optional[bool] = getattr(self, "_debug", None)
300+
previous_debug: bool | None = getattr(self, "_debug", None)
301301
self._debug = value
302302

303303
def enable_http_logging():

pinecone/config/pinecone_config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from typing import Optional, Dict
21
import logging
32
import json
43
import os
@@ -13,9 +12,9 @@
1312
class PineconeConfig:
1413
@staticmethod
1514
def build(
16-
api_key: Optional[str] = None,
17-
host: Optional[str] = None,
18-
additional_headers: Optional[Dict[str, str]] = {},
15+
api_key: str | None = None,
16+
host: str | None = None,
17+
additional_headers: dict[str, str] | None = {},
1918
**kwargs,
2019
) -> Config:
2120
host = (

pinecone/db_control/db_control.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from __future__ import annotations
2+
13
import logging
2-
from typing import Optional, TYPE_CHECKING
4+
from typing import TYPE_CHECKING
35

46
from pinecone.core.openapi.db_control.api.manage_indexes_api import ManageIndexesApi
57
from pinecone.openapi_support.api_client import ApiClient
@@ -42,16 +44,16 @@ def __init__(
4244
)
4345
""" :meta private: """
4446

45-
self._index_resource: Optional["IndexResource"] = None
47+
self._index_resource: "IndexResource" | None = None
4648
""" :meta private: """
4749

48-
self._collection_resource: Optional["CollectionResource"] = None
50+
self._collection_resource: "CollectionResource" | None = None
4951
""" :meta private: """
5052

51-
self._restore_job_resource: Optional["RestoreJobResource"] = None
53+
self._restore_job_resource: "RestoreJobResource" | None = None
5254
""" :meta private: """
5355

54-
self._backup_resource: Optional["BackupResource"] = None
56+
self._backup_resource: "BackupResource" | None = None
5557
""" :meta private: """
5658

5759
super().__init__() # Initialize PluginAware

pinecone/db_control/db_control_asyncio.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
from __future__ import annotations
2+
13
import logging
2-
from typing import Optional, TYPE_CHECKING
4+
from typing import TYPE_CHECKING
35

46
from pinecone.core.openapi.db_control.api.manage_indexes_api import AsyncioManageIndexesApi
57
from pinecone.openapi_support import AsyncioApiClient
@@ -36,16 +38,16 @@ def __init__(self, config: "Config", openapi_config: "OpenApiConfiguration") ->
3638
)
3739
""" :meta private: """
3840

39-
self._index_resource: Optional["IndexResourceAsyncio"] = None
41+
self._index_resource: "IndexResourceAsyncio" | None = None
4042
""" :meta private: """
4143

42-
self._collection_resource: Optional["CollectionResourceAsyncio"] = None
44+
self._collection_resource: "CollectionResourceAsyncio" | None = None
4345
""" :meta private: """
4446

45-
self._restore_job_resource: Optional["RestoreJobResourceAsyncio"] = None
47+
self._restore_job_resource: "RestoreJobResourceAsyncio" | None = None
4648
""" :meta private: """
4749

48-
self._backup_resource: Optional["BackupResourceAsyncio"] = None
50+
self._backup_resource: "BackupResourceAsyncio" | None = None
4951
""" :meta private: """
5052

5153
@property

0 commit comments

Comments
 (0)