Guide · Pinecone

Migrate Pinecone Python SDK from v2 to v3

Version 3 of Pinecone's Python SDK removed pinecone.init() and its global state in favor of a Pinecone client object, and changed how indexes are created, listed and targeted. The package has since been renamed from pinecone-client to pinecone.

v3.0.0 was released on January 16, 2024. It moved the SDK to a new control plane API at api.pinecone.io, which ended the rigid one-to-one link between API keys and environments, and it was the first release that could create serverless indexes. Code written for 2.2.x fails on the first pinecone.init() call. This guide covers the v3 changes, which every later version builds on, and ends with what changed after v3 so you know where to go next. It follows Pinecone's release notes, READMEs and migration guides, linked at the end.

What changed

  • The PyPI package is now pinecone. pinecone-client is deprecated and its last release, 6.0.0, only tells you to switch.
  • pinecone.init(api_key=..., environment=...) is replaced by pc = Pinecone(api_key=...). There is no environment argument.
  • create_index takes a spec: ServerlessSpec(cloud, region) or PodSpec(environment, pod_type, ...). Pod settings such as pod_type and replicas moved into PodSpec.
  • Indexes are targeted with pc.Index(host=...) (recommended) or pc.Index(name).
  • list_indexes() returns full descriptions; chain .names() for the old list of names.
  • describe_index returns a new shape, with settings under spec and host at the top level.
  • query() needs keyword arguments, and the queries batch argument is gone.

Migration steps

  1. Swap the package

    pip uninstall pinecone-client
    pip install pinecone

    Uninstall first. Pinecone warns that leaving pinecone-client installed next to pinecone can lead to confusing interactions between the two, and its docs say upgrading under the old name stops working from v6.0.0. The import name stays pinecone. v3 itself still shipped as pinecone-client, so lock files pinned to pinecone-client==3.x are already past this guide's code changes but not past the rename.

  2. Replace pinecone.init with a client

    Before

    import pinecone
    
    pinecone.init(api_key="YOUR_API_KEY", environment="us-west1-gcp")
    pinecone.describe_index("example-index")

    After

    from pinecone import Pinecone
    
    pc = Pinecone(api_key="YOUR_API_KEY")  # or Pinecone() to read PINECONE_API_KEY
    pc.describe_index("example-index")

    Control-plane functions that used to be module-level (create_index, describe_index, delete_index, configure_index, the collection functions) are now methods on the client. Because configuration lives on the instance, one process can use several API keys. Delete PINECONE_ENVIRONMENT settings that only existed for init.

  3. Pass a spec to create_index

    Before

    pinecone.create_index("example-index", dimension=1536, metric="cosine")

    After

    from pinecone import Pinecone, ServerlessSpec
    
    pc = Pinecone()
    pc.create_index(
        name="example-index",
        dimension=1536,
        metric="cosine",
        spec=ServerlessSpec(cloud="aws", region="us-west-2"),
    )

    In v3, pod indexes use spec=PodSpec(environment="us-west-2", pod_type="p1.x1"), and metadata_config and replicas move inside PodSpec too. Index creation code is where later versions changed most, so read the callout in the last step before you write new PodSpec calls.

  4. Target indexes by host

    Before

    index = pinecone.Index("example-index")
    index.upsert(vectors=[("vec1", [0.1, 0.2, 0.3, 0.4], {"genre": "drama"})])

    After

    index = pc.Index(host=os.environ["INDEX_HOST"])
    index.upsert(vectors=[("vec1", [0.1, 0.2, 0.3, 0.4], {"genre": "drama"})])

    Get the host once from pc.describe_index("example-index").host or the Pinecone console and store it in configuration. pc.Index("example-index") also works, but the client then makes an extra control-plane call to look up the host, which Pinecone does not recommend for production. As the example shows, the upsert call itself keeps its shape.

  5. Chain .names() on list calls

    Before

    if "example-index" not in pinecone.list_indexes():
        pinecone.create_index("example-index", dimension=1536)

    After

    if "example-index" not in pc.list_indexes().names():
        pc.create_index(
            name="example-index",
            dimension=1536,
            metric="cosine",
            spec=ServerlessSpec(cloud="aws", region="us-west-2"),
        )

    list_indexes() now returns an IndexList of full descriptions (name, dimension, metric, status, host, spec), and list_collections() changed the same way. Without .names(), an in check compares a name with description objects instead of names, so the code above can try to create an index that already exists. Code reading describe_index() results needs the same review: pod settings are under spec.

  6. Query with keyword arguments

    Before

    results = index.query([0.1, 0.2, 0.3, 0.4], top_k=10, include_metadata=True)

    After

    results = index.query(vector=[0.1, 0.2, 0.3, 0.4], top_k=10, include_metadata=True)

    top_k became the first positional parameter, so passing the vector positionally produces a confusing API error about duplicate top_k values. The queries argument for several vectors in one call was removed; loop over your vectors instead. gRPC users import the client from the subpackage: from pinecone.grpc import PineconeGRPC.

  7. Know what changed after v3

    If you install the latest pinecone, the v3 call shapes above still run, but two later majors changed the canonical forms. v9 grouped control-plane calls under namespaces (pc.indexes.create, pc.indexes.list), made pc.index("name") lowercase, raised the minimum Python to 3.10, and kept the v3-style methods as shims so you can move gradually.

How to find every affected call

Every v2 control-plane call goes through the pinecone module, so ast-grep finds them reliably, including calls split across lines:

sg run --pattern 'pinecone.init($$$ARGS)' --lang python
sg run --pattern 'pinecone.Index($$$ARGS)' --lang python
sg run --pattern 'pinecone.create_index($$$ARGS)' --lang python
sg run --pattern 'pinecone.list_indexes()' --lang python
sg run --pattern 'pinecone.$METHOD($$$ARGS)' --lang python
sg run --pattern '$INDEX.query($VECTOR, $$$REST)' --lang python

The broad pinecone.$METHOD pattern catches the rest of the module-level calls (describe_index, configure_index, create_collection and so on). The last pattern finds queries whose first argument is positional; it can also match other libraries' query methods, so check each hit. Also check dependency files for pinecone-client, including transitive pins from other packages.

Testing the change

  • Use a separate test project and API key, and create a small serverless index for the test run rather than pointing tests at production indexes.
  • Replace mocks of pinecone.init and pinecone.Index with a fake Pinecone client injected into your code; old mocks pass against code that no longer exists.
  • Test the paths that branch on list_indexes(). The missing .names() bug does not raise, it just takes the wrong branch.
  • Set PINECONE_DEBUG_CURL='true' while debugging to see the requests the client sends.

Automating it

We found no official codemod for this upgrade in Pinecone's release notes, READMEs or docs. The migration is a manual edit guided by the v3.0.0 migration guide, and later ones for v9 and v10.

Versyka finds the call sites deterministically with ast-grep patterns like the ones above, rewrites them along with the code that reads the results (the .names() checks, describe_index fields, mocks), then runs your repository's own tests in an isolated virtualenv inside a disposable sandbox. It opens a pull request only if they pass.

Was this guide helpful?

Check your own repository

Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.

Sources