mrkeyoor.com_
Sun 20 Sept 13:40 UTC
PyPIDataupdated 20 Sept 2026

elasticsearch review

elasticsearch 9.5.0 is the official Python transport and API client for Elasticsearch. It turns Python values into REST requests, keeps a connection pool across nodes, and exposes sync and async clients plus bulk, scan, ES|QL, and `elasticsearch.dsl` helpers. It does not run a search engine or validate the meaning of most query JSON locally. Version 9.5.0 adds bindings for encryption reset and inference region policies, extends several server APIs, adds DSL doc-values options, and fixes opaque-ID propagation in `async_scan`. Our install was pure Python and shipped py.typed.

Verdict

elasticsearch 9.5.0 installed in 0.3 seconds and used 10 MB across 10 packages in our sandbox, with typed APIs and no audit findings. Use it for an actual Elasticsearch cluster; if you are still choosing the search server, compare the operational cost before this official client decides the architecture for you.

We installed it

Lab card: what happened when we installed elasticsearchScreenshot of elasticsearch documentation
Install✓ · 0.3s10 packages on disk · 10 MB
Importimport elasticsearch in 1.14s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does elasticsearch install cleanly?

Yes. In a fresh container with an empty cache, pip install elasticsearch finished in 0.3s, leaving 10 packages and 10 MB on disk. pip-audit reported no known vulnerabilities.

What does elasticsearch need to run?

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

elasticsearch or opensearch-py: which should you use?

opensearch-py: Use it when the remote service is OpenSearch, including AWS deployments that expose OpenSearch APIs. elasticsearch 9.5.0 installed in 0.3 seconds and used 10 MB across 10 packages in our sandbox, with typed APIs and no audit findings.

When should you not use elasticsearch?

The cluster is OpenSearch or AWS OpenSearch Service. opensearch-py targets that product, while Elastic's client performs Elasticsearch-specific compatibility and product checks.

API stability3/5Elastic states that a language client works with equivalent and later minor server releases, while full support for a feature arrives in the equivalent client version. The familiar `Elasticsearch`, `AsyncElasticsearch`, and helper entry points remain in 9.5.0, and this release is mostly additive. Major upgrades still require deliberate order: Elastic tells users to upgrade the server first, then the Python client. Request-option conventions have also changed enough that older snippets can be wrong on 9.x.
Docs4/5The official material covers secure connections, Elastic Cloud, transport settings, bulk helpers, async clients, DSL queries, and a generated reference for each server API. Release notes identify the exact methods and fixes added in 9.5.0. The client pages cannot explain every mapping, analyzer, query, aggregation, and cluster behavior; a rejected request often sends the reader into the server reference. The documentation is accurate, though it assumes Elasticsearch knowledge.
Maintenance5/5Version 9.5.0 was published on August 4, 2026, and GitHub showed the unarchived repository pushed on August 24, 2026. The tracker contains 62 open issues and pull requests. The current release combines generated bindings for new server endpoints with a specific `async_scan` trace fix, showing work on both coverage and client helpers. Elastic also keeps named `elasticsearch7` and `elasticsearch8` packages for applications that must carry older client lines.
Ecosystem5/5Registry data records 12,876,739 weekly downloads, and the repository has 4,383 GitHub stars. The package is the expected Python dependency for Elasticsearch and now incorporates the former DSL package interface alongside transport, bulk, scan, and ES|QL helpers. That ecosystem is specific to Elastic's server. OpenSearch has its own maintained client, and choosing this package should not be treated as proof that an OpenSearch endpoint will behave the same way.

Use it if

  • The server is Elasticsearch 9.x and application code needs API coverage released alongside that server line.
  • A large ingestion job needs `streaming_bulk`, per-item failures, and bounded chunks rather than a hand-written HTTP loop.
  • An asyncio service needs persistent Elasticsearch connections without blocking its event loop during network calls.
  • Your query code benefits from the lazy Search and Document interface now included under `elasticsearch.dsl`.
Skip it if

Setup reality

We installed elasticsearch 9.5.0 in a fresh Python 3.12 Bookworm container. The install finished in 0.3 seconds and occupied 10 MB across 10 packages. Its metadata declares 44 direct dependencies, and the distribution is pure Python with py.typed. import elasticsearch succeeded in 1.14 seconds. pip-audit reported zero known vulnerabilities. The package requires Python 3.10 or later and uses the Apache Software License.

The wheel contains no server. Connect with a full URL plus an API key, basic credentials, or Elastic Cloud's cloud_id. A private HTTPS cluster usually needs its CA certificate through ca_certs; disabling certificate checks turns a configuration problem into a security problem. The 9.5 client should follow the server upgrade when crossing a major, because Elastic documents complete feature support for equivalent client and server releases.

Transport options belong on the constructor or a client returned by .options(). That detail breaks copied examples which pass request_timeout, ignore_status, or opaque_id as API parameters. Reuse one client so persistent connections and node health tracking work. In async code, keep one AsyncElasticsearch for the application lifetime and call close() during shutdown. Release 9.5.0 fixes async_scan so its opaque ID reaches the underlying requests.

Bulk helpers report success per action, so inspect failed items instead of treating the request as one result. Retrying HTTP 429 can help; retrying a mapping rejection unchanged cannot. Search visibility also follows the index refresh cycle. A document accepted by index() may be available through get() before a search finds it. Tests and low-volume workflows can use refresh='wait_for', while sustained ingestion normally lets scheduled refresh handle visibility.

Patterns

Connect with an API key and CA connect-securely

import os
from elasticsearch import Elasticsearch

client = Elasticsearch(
    'https://localhost:9200',
    api_key=os.environ['ELASTIC_API_KEY'],
    ca_certs='./http_ca.crt',
)
print(client.info()['version']['number'])

The URL needs its scheme. Keep certificate verification enabled and trust the cluster's CA instead of setting `verify_certs=False`.

Open an Elastic Cloud connection connect-elastic-cloud

import os
from elasticsearch import Elasticsearch

client = Elasticsearch(
    cloud_id=os.environ['ELASTIC_CLOUD_ID'],
    api_key=os.environ['ELASTIC_API_KEY'],
)
print(client.cluster.health()['status'])

`cloud_id` locates the deployment; the API key supplies authority. Issue that key only the index and cluster privileges this service uses.

Create explicit field mappings create-mapping

client.indices.create(
    index='articles',
    mappings={
        'properties': {
            'title': {'type': 'text', 'analyzer': 'english'},
            'tags': {'type': 'keyword'},
            'published_at': {'type': 'date'},
        }
    },
    settings={'number_of_shards': 1},
)

Changing an existing field type usually requires a new index and reindex. Decide which values need text analysis and which need exact keyword matching.

Wait until an indexed document is searchable index-visible-document

client.index(
    index='articles',
    id='post-42',
    document={'title': 'Shard sizing', 'tags': ['ops']},
    refresh='wait_for',
)

result = client.get(index='articles', id='post-42')
print(result['_source'])

`refresh='wait_for'` adds write latency and is best reserved for tests or low-volume workflows that need immediate search visibility.

Score text and filter exact tags search-and-filter

response = client.search(
    index='articles',
    query={
        'bool': {
            'must': [{'match': {'title': 'shard sizing'}}],
            'filter': [{'term': {'tags': 'ops'}}],
        }
    },
    sort=[{'published_at': 'desc'}],
    size=20,
)
for hit in response['hits']['hits']:
    print(hit['_id'], hit['_source'])

A term query expects an exact indexed token. Map tag-like values as keyword rather than analyzed text.

Inspect every streaming bulk result bulk-index

from elasticsearch.helpers import streaming_bulk

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

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

A 429 may succeed after a bounded retry. A mapping rejection needs changed data or mapping, so keep it out of an endless retry queue.

Process a large match set scan-results

from elasticsearch.helpers import scan

for hit in scan(
    client,
    index='articles',
    query={'query': {'range': {'published_at': {'lt': 'now-1y'}}}},
    scroll='2m',
):
    archive(hit['_source'])

A scroll holds cluster resources. Finish promptly, or use a point in time with `search_after` for stable deep pagination.

Set transport options for one request request-options

slow = client.options(request_timeout=120, opaque_id='nightly-archive')
result = slow.search(
    index='articles',
    query={'match_all': {}},
    size=0,
)

client.options(ignore_status=404).indices.delete(index='old-preview')

Current clients put transport behavior on `.options()`. `request_timeout` and `ignore_status` are not search-body parameters.

Close async connections during teardown async-client

import os
from elasticsearch import AsyncElasticsearch

async def count_articles() -> int:
    client = AsyncElasticsearch(
        'https://localhost:9200',
        api_key=os.environ['ELASTIC_API_KEY'],
        ca_certs='./http_ca.crt',
    )
    try:
        result = await client.count(index='articles')
        return result['count']
    finally:
        await client.close()

A server should keep one async client for its lifetime and close it at shutdown, rather than create a connection pool per request.

Compose a lazy DSL search dsl-query

from elasticsearch.dsl import Search

query = (
    Search(using=client, index='articles')
    .query('match', title='shard sizing')
    .filter('term', tags='ops')
    .sort('-published_at')
)
for hit in query[:20].execute():
    print(hit.meta.id, hit.title)

Search objects build a request lazily. The network call happens on `execute()` or when the result is otherwise evaluated.

Count top keyword values aggregate-keywords

response = client.search(
    index='articles',
    size=0,
    aggs={
        'popular_tags': {
            'terms': {'field': 'tags', 'size': 20}
        }
    },
)
for bucket in response['aggregations']['popular_tags']['buckets']:
    print(bucket['key'], bucket['doc_count'])

Terms aggregation requires a keyword-compatible field. `size=0` omits document hits when only buckets are needed.

Execute an ES|QL pipeline run-esql

response = client.esql.query(
    query='''
      FROM articles
      | WHERE views > 100
      | STATS total = SUM(views) BY tags
      | SORT total DESC
      | LIMIT 10
    ''',
    format='json',
)
columns = [column['name'] for column in response['columns']]
for values in response['values']:
    print(dict(zip(columns, values)))

ES|QL returns column metadata and row arrays rather than search hits. Its available syntax follows the connected server version.

Alternatives

PackageRegistryPick it when
opensearch-pyPyPIUse it when the remote service is OpenSearch, including AWS deployments that expose OpenSearch APIs.
meilisearchPyPIChoose it when a smaller typo-tolerant search service meets the application requirement.
typesensePyPIChoose it for faceted application search when its simpler schema and operational model are enough.
elasticsearch8PyPIUse the separately named 8.x package when one process must retain an Elasticsearch 8 client beside version 9.

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.