mrkeyoor.com_
Sun 20 Sept 21:36 UTC
PyPIDataupdated 20 Sept 2026

opensearch-py review

opensearch-py 3.2.0 is the Python transport and generated API client for OpenSearch clusters. Application code imports `opensearchpy`, creates a synchronous or asynchronous client, and calls document, search, index, cluster, security, and plugin endpoints using keyword arguments and dictionary request bodies. Helper generators handle bulk indexing and scan iteration. The client also contains AWS Signature Version 4 authentication classes for managed OpenSearch and Serverless. Version 3.2.0 refreshes methods from the current OpenSearch API specification, adds ML Commons documentation and newer protobuf definitions, terminates a multiprocessing pool more decisively, and fixes AWS signing so existing headers plus `X-Amz-Content-SHA256` are included correctly.

Verdict

opensearch-py 3.2.0 is the direct choice for Python code talking to OpenSearch, particularly when AWS request signing or OpenSearch-only endpoints are involved. It does not hide cluster operations or turn raw search payloads into a domain model, and it should not be pointed at Elasticsearch by habit.

We installed it

Lab card: what happened when we installed opensearch-pyScreenshot of opensearch-py documentation
Install✓ · 0.5s13 packages on disk · 25 MB
Importimport opensearchpy in 0.73s · pure Python · py.typed · requires Python >=3.10, <4
Known vulns0(pip-audit)

Answers from our run

Does opensearch-py install cleanly?

Yes. In a fresh container with an empty cache, pip install opensearch-py finished in 0.5s, leaving 13 packages and 25 MB on disk. pip-audit reported no known vulnerabilities.

What does opensearch-py need to run?

Python >=3.10, <4, and nothing compiled: it is pure Python. In our run import opensearchpy succeeded in 0.73s, and the package ships py.typed for type checkers.

opensearch-py or elasticsearch: which should you use?

elasticsearch: Use it for Elasticsearch clusters so the client's generated endpoints and compatibility rules match that server. opensearch-py 3.2.0 is the direct choice for Python code talking to OpenSearch, particularly when AWS request signing or OpenSearch-only endpoints are involved.

When should you not use opensearch-py?

The server is Elasticsearch. OpenSearch and Elasticsearch have diverged in endpoints, mappings, authentication, compatibility checks, and release cadence; use the client published for the actual cluster.

API stability3/5The core client, named subclients, dictionary bodies, transport settings, and bulk helpers retain the shape inherited from earlier releases. Yet generated calls move with the OpenSearch specification, and version 3 made every generated API argument keyword-only. OpenSearch-specific mappings and plugin endpoints also change with server releases. Compatibility is documented, but applications should pin the client, name every argument, and run integration tests against the actual cluster version before an upgrade.
Docs4/5The project links a user guide, generated API reference, compatibility table, working samples, benchmarks, and product documentation. Material covers TLS, basic authentication, AWS signing, sync and async clients, bulk helpers, point-in-time search, proxies, and plugin APIs. The split across the repository, generated reference, and main OpenSearch docs takes some navigation, and older search results may still show positional arguments that version 3 rejects.
Maintenance4/5Release 3.2.0 shipped on April 27, 2026 with an API-spec refresh, protobuf update, documentation work, multiprocessing cleanup, and two AWS signing fixes. GitHub reports 469 stars, 116 open issues and pull requests, an unarchived repository, and a push on August 20. The OpenSearch organization documents maintainer, admin, security, compatibility, and release processes. Activity is current, though the open queue is substantial relative to the repository's contributor footprint.
Ecosystem4/5The client covers core OpenSearch REST APIs, product plugins, synchronous and asynchronous connections, AWS IAM signing, bulk and scan helpers, and a separate DSL package. Its `py.typed` marker lets type checkers inspect the shipped annotations, and the Apache-2.0 license matches the server project. The surrounding Python shelf is smaller than Elasticsearch's, and many integrations expose OpenSearch as one backend among several, so unusual plugin workflows often fall back to raw transport calls.

Use it if

  • The target is OpenSearch, Amazon OpenSearch Service, or OpenSearch Serverless and client methods should follow that product's APIs.
  • AWS IAM credentials must sign requests with the `es` or `aoss` service name through a supported connection class.
  • Bulk indexing, scans, point-in-time paging, index administration, and OpenSearch plugin endpoints belong in one Python client.
  • A Python service needs both synchronous urllib3 transport and an optional aiohttp-based async client with similar request shapes.
Skip it if

Setup reality

We installed opensearch-py 3.2.0 in a fresh Python 3.12 Bookworm container. It completed in 0.5 seconds, left 13 packages, and used 25 MB. Package metadata declares 28 direct dependencies, Python 3.10 through 3.x below 4, Apache-2.0 licensing, and a pure-Python distribution with py.typed. import opensearchpy succeeded in 0.73 seconds. pip-audit found no known vulnerabilities. The install name contains a hyphen; the import name does not.

A local TLS client needs host, port, authentication, certificate verification, and a CA path. Do not carry verify_certs=False from a quick-start cluster into shared code. For AWS, obtain refreshable credentials from the normal boto3 chain, choose service es for a managed domain or aoss for Serverless, and pair the signer with its connection class. Version 3.2.0 repairs signed-header handling, so older clients should be upgraded before investigating mysterious signature mismatches.

The client keeps a connection pool. Reuse one instance per process instead of constructing a client per request, set timeouts and retry behavior for the workload, and close asynchronous clients so aiohttp sessions are released. Sniffing may be wrong behind load balancers or managed endpoints because nodes can advertise addresses the application cannot reach. Bulk helpers are lazy where they return generators; if code never consumes parallel_bulk(), it never sends the documents. Capture item-level failures rather than treating one HTTP success as proof that every operation succeeded.

OpenSearch refreshes make recent writes searchable, but refresh=True on every index call is an expensive test habit. Use bulk operations and the cluster's refresh policy for ingestion. Deep from pagination hits result-window limits; use search_after with a stable sort and a point in time when results must remain consistent. Keep mappings versioned with the application. Vector fields and plugin APIs vary with the cluster release, so check the compatibility document and test against the same server version deployed.

Patterns

Connect with a verified cluster certificate connect-with-tls

import os
from opensearchpy import OpenSearch

client = OpenSearch(
    hosts=[{'host': 'search.example.com', 'port': 9200}],
    http_auth=('app', os.environ['OPENSEARCH_PASSWORD']),
    use_ssl=True,
    verify_certs=True,
    ca_certs='./root-ca.pem',
    pool_maxsize=20,
)

print(client.info()['version'])

The PyPI name is `opensearch-py`, while Python imports `opensearchpy`. Supply the trusted CA rather than suppressing certificate verification.

Authenticate to an AWS managed domain sign-aws-requests

import boto3
from opensearchpy import OpenSearch, Urllib3AWSV4SignerAuth, Urllib3HttpConnection

credentials = boto3.Session().get_credentials()
auth = Urllib3AWSV4SignerAuth(credentials, 'us-west-2', 'es')

client = OpenSearch(
    hosts=[{'host': 'domain.us-west-2.es.amazonaws.com', 'port': 443}],
    http_auth=auth,
    use_ssl=True,
    verify_certs=True,
    connection_class=Urllib3HttpConnection,
)

Use service name `es` for managed domains and `aoss` for Serverless. Match the urllib3 signer to `Urllib3HttpConnection`; other transports have their own signer classes.

Create mappings with named arguments create-index

client.indices.create(
    index='movies',
    body={
        'settings': {'number_of_shards': 2, 'number_of_replicas': 1},
        'mappings': {'properties': {
            'title': {'type': 'text'},
            'director': {'type': 'keyword'},
            'year': {'type': 'integer'},
        }},
    },
)

Version 3 generated methods require keyword arguments. Mapping types become an index contract and normally require reindexing when changed incompatibly.

Index and retrieve one document write-and-read-document

client.index(
    index='movies',
    id='1',
    body={'title': 'Moneyball', 'director': 'Bennett Miller', 'year': 2011},
)

client.indices.refresh(index='movies')
document = client.get(index='movies', id='1')['_source']

client.update(index='movies', id='1', body={'doc': {'year': 2012}})

A refresh makes the write visible to search, while `get()` is real-time by default. Avoid forcing a refresh after each document in an ingestion loop.

Combine full-text matching and a filter run-bool-query

response = client.search(
    index='movies',
    body={
        'size': 10,
        'query': {'bool': {
            'must': [{'multi_match': {
                'query': 'miller', 'fields': ['title^2', 'director'],
            }}],
            'filter': [{'range': {'year': {'gte': 2000}}}],
        }},
        'sort': [{'year': 'desc'}, {'_id': 'asc'}],
    },
)

for hit in response['hits']['hits']:
    print(hit['_score'], hit['_source']['title'])

Filters do not contribute to relevance score. A stable secondary sort becomes important when this query later moves to `search_after` pagination.

Inspect every bulk item result bulk-index

from opensearchpy.helpers import streaming_bulk

def actions(rows):
    for row in rows:
        yield {'_index': 'movies', '_id': row['id'], '_source': row}

for ok, item in streaming_bulk(
    client, actions(rows), chunk_size=500, raise_on_error=False,
):
    if not ok:
        log.error('bulk failure: %r', item)

Bulk HTTP responses can contain a mix of successful and failed operations. Consume the generator and record failed items for retry or quarantine.

Iterate a large result set with scan scan-matches

from opensearchpy.helpers import scan

for hit in scan(
    client,
    index='movies',
    query={'query': {'range': {'year': {'lt': 2000}}}},
    size=1000,
    scroll='2m',
):
    archive(hit['_source'])

Scroll contexts hold cluster resources and segment references. Keep the timeout short enough for normal progress and avoid ordered scanning unless it is required.

Use point-in-time search with `search_after` page-with-pit

pit_id = client.create_pit(index='movies', params={'keep_alive': '2m'})['pit_id']
try:
    body = {
        'size': 100,
        'query': {'match_all': {}},
        'pit': {'id': pit_id, 'keep_alive': '2m'},
        'sort': [{'year': 'desc'}, {'_id': 'asc'}],
    }
    while True:
        hits = client.search(body=body)['hits']['hits']
        if not hits:
            break
        consume(hits)
        body['search_after'] = hits[-1]['sort']
finally:
    client.delete_pit(body={'pit_id': [pit_id]})

Do not pass `index=` when the point in time already identifies the indexes. Delete it in `finally` so cluster resources are not held until expiry after an error.

Create and query a vector field search-knn-vectors

client.indices.create(index='embeddings', body={
    'settings': {'index': {'knn': True}},
    'mappings': {'properties': {
        'vector': {
            'type': 'knn_vector', 'dimension': 768,
            'method': {'name': 'hnsw', 'space_type': 'cosinesimil', 'engine': 'faiss'},
        },
        'text': {'type': 'text'},
    }},
})

response = client.search(index='embeddings', body={
    'size': 10,
    'query': {'knn': {'vector': {'vector': embedding, 'k': 10}}},
})

Vector mapping names and supported engines depend on the OpenSearch k-NN plugin and server version. The dimension must match every indexed query and document vector.

Close the aiohttp-backed client use-async-client

# python -m pip install 'opensearch-py[async]'
import asyncio
from opensearchpy import AsyncOpenSearch

async def main():
    client = AsyncOpenSearch(
        hosts=[{'host': 'search.example.com', 'port': 9200}],
        use_ssl=True, verify_certs=True, ca_certs='./root-ca.pem',
    )
    try:
        return await client.search(index='movies', body={'query': {'match_all': {}}})
    finally:
        await client.close()

result = asyncio.run(main())

The async extra supplies aiohttp. Always close the client, including error paths, or Python reports an unclosed session and connector.

Build a query through opensearch-dsl compose-with-dsl

from opensearchpy import Search, Q

search = (
    Search(using=client, index='movies')
    .filter('term', director='Bennett Miller')
    .query(Q('match', title='moneyball') | Q('match', title='capote'))
    .sort('-year')[:20]
)
search.aggs.bucket('per_year', 'terms', field='year')

for hit in search.execute():
    print(hit.meta.score, hit.title)

Install the separate `opensearch-dsl` distribution. Search-building methods return new objects, while aggregation builder calls mutate their aggregation tree.

Reach an endpoint missing from generated methods call-raw-endpoint

policies = client.transport.perform_request(
    'GET',
    '/_plugins/_ism/policies',
    params={'pretty': 'true'},
)

client.transport.perform_request(
    'PUT',
    '/_plugins/_ism/policies/archive',
    body={'policy': {'description': 'archive policy', 'states': []}},
)

Raw transport calls skip generated parameter checks and tie the code directly to a REST path. Cover them with cluster-version integration tests.

Alternatives

PackageRegistryPick it when
elasticsearchPyPIUse it for Elasticsearch clusters so the client's generated endpoints and compatibility rules match that server.
opensearch-dslPyPIUse it on top of OpenSearch when composable query objects and document mappings are preferable to hand-written dictionaries.
boto3PyPIUse it for AWS control-plane tasks such as creating or describing domains; opensearch-py is mainly the cluster data-plane client.

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.