elasticsearch-py sends dictionaries and owns the transport underneath
elasticsearch-py is the official Python client for Elasticsearch, providing typed query construction, index and document lifecycle APIs, bulk operations, async compatibility, and version-aware client behavior.
At a glance
- What is it?
- The official Python client for Elasticsearch handles node discovery, connection pooling and JSON translation, and it takes a strict position on which server version its parameters match. That strictness is the decision to make before you depend on it.
- Who is it for?
- Adopt elasticsearch-py when you want Elastic's own transport, node discovery, connection pooling and time based penalization instead of a few lines of requests. Skip it when you need features from a server minor version newer than your client branch, since new server features need an equivalent client version, and when you are below Python 3.10.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- elastic/elasticsearch-py4,397 ★ · +15
| Repository | 2026-07-28 | 2026-11-13 |
|---|---|---|
| elastic/elasticsearch-py | 4.4k | 4.4k |
15 Stars in the last 108 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Why this client exists instead of a requests.post call
Elasticsearch ships a REST API, and any HTTP library can reach it. What elasticsearch-py adds is the layer between a single endpoint and the cluster topology. It discovers cluster nodes, keeps persistent connections open, spreads requests across them, and refuses to retry a node whose connection failed until a timeout expires. Those four jobs are the reason the library is 9.x while a hand-rolled POST loop is ten lines.
The README states the case as a feature list: automatic discovery of cluster nodes, persistent connections, load balancing with a pluggable selection strategy, failed connection penalization based on time, TLS and HTTP authentication, thread safety across requests. Each of those is a failure you would otherwise handle yourself.
The audience is Python code that talks to Elasticsearch, from a service to a notebook. What the client does not do is model your documents. Search queries are plain dictionaries that go to the server as JSON, and responses come back as dictionaries. The type hints tell you the response shape, not your domain.
The elastic-transport dependency does the node and socket work
pyproject.toml names elastic-transport>=9.4.1,<10 as the only first-party dependency that matters here, alongside python-dateutil, typing-extensions, sniffio and anyio. The transport layer is where node selection, connection pooling, TLS options and retry behaviour live, so the client's own surface stays thin. Configuration for hosts, sniffing and retries arrives from that package rather than from the API namespace.
sniffio and anyio in that dependency list are the tell for the async story. The async extra adds aiohttp.
One detail is easy to skim past: the optional dependency for requests is pinned with an exclusion, requests>=2.4.0, !=2.32.2, <3.0.0. The default stack is not requests. A specific version of the requests library is ruled out by name in the packaging metadata.
A local cluster first, since the README ships no install command
There is no pip line in the README. The Installation section sends you to the Elastic getting started documentation for the client, and to the Elasticsearch download page or Elastic Cloud sign-up for the server itself.
What the README does give you is a one-liner for a local Elasticsearch and Kibana, and it is the fastest way to have something for a first query:
curl -fsSL https://elastic.co/start-local | shThat script starts Elasticsearch on http://localhost:9200 and Kibana on http://localhost:5601. Once port 9200 answers, the client and the connecting, index, get, search, update, delete and delete-index steps all live in the same getting started guide, linked from the README as separate anchors. The client package itself is published on PyPI as elasticsearch, with classifiers covering Python 3.10 through 3.14, CPython and PyPy, and requires-python set to >=3.10.
Extras are opt-in, so each one changes the data path
pyproject.toml defines optional dependency groups, and each swaps part of the runtime rather than adding a convenience. async pulls aiohttp>=3,<4 and changes the transport to the asyncio stack. orjson swaps the JSON encoder. pyarrow adds the pyarrow package. vectorstore_mmr, described in the file itself as Maximal Marginal Relevance for search results, adds numpy and simsimd so ranking can be reweighted client-side instead of on the server.
That last one is the interesting case. It means relevance tuning is not locked to the cluster. The dependency exists to run MMR locally, so a retrieval pipeline can diversify its own result set after the query returns.
Because each extra is separate, the default install is smaller.
Forward compatibility that stops at new parameters
The README makes a distinction that decides upgrade planning. Language clients are forward compatible: each client version works with equivalent and later minor versions of Elasticsearch without breaking. Then it says compatibility does not imply full feature parity. New Elasticsearch features are supported only in equivalent client versions. An 8.12 client fully supports 8.12 features and works with 8.13, but does not support 8.13 features.
So the failure mode is not a crash on upgrade, it is a missing or rejected parameter when you reach for something the server can do and your client cannot name. That is the cost of a typed surface, and it is paid at the moment you adopt a new server capability.
The upgrade order is explicit: upgrade Elasticsearch first, then the Python client. For code that has to span client versions, older lines are still published as separate distributions named elasticsearch7 and elasticsearch8, so a migration can be staged rather than done in one commit.
The compatibility table contradicts its own upgrade tip
The table maps server versions to client branches in four rows: main to main, 9.x to 9.x, 9.x to 8.x, and 8.x to 8.x. Read plainly, that third row says an 8.x client runs against a 9.x server, which matches the tip that says to upgrade the server first and the client second.
The forward compatibility statement covers later minor versions, not later major versions. A skim of the table would read it as permission to stay on the 8.x client while the cluster moves to 9.x. A careful reader should treat that row as unverified for a major version jump and pin the branch to the server.
Maintenance signal is thin here: the repository is not archived, the last push was on 2026-11-02, and the package is licensed Apache-2.0.
elasticsearch-dsl and the raw transport make different trade-offs
The repository ships an example directory per style of use, including examples/dsl, examples/bulk-ingest, examples/quotes and examples/fastapi-apm. The dsl example is the alternative worth comparing, because it sits on top of the client rather than replacing it.
The official client takes a dictionary for a query body and passes it through. The repository also ships a DSL example under examples/dsl.
The other difference is blast radius. The client plus elastic-transport owns discovery, pooling and retries, so a dependency bump changes the transport version range with it, pinned as elastic-transport>=9.4.1,<10. A hand-rolled requests call has no such coupling and no node awareness at all.
Where the client is the wrong tool
Three cases fail first. If your team needs a server feature newer than your client branch, the client will not carry the parameter, and the fix is a client upgrade rather than a code change. If your interpreter predates the requires-python floor of >=3.10, there is no supported install at all. If you plan to hold several major server lines in one process, the answer is running separate client distributions, one of elasticsearch7, elasticsearch8 or elasticsearch, rather than one client juggling branches.
The README also says compatibility across minor versions holds for default distributions and without guarantees, so a rolling server upgrade is not a promise the client makes on its own.
What the documentation does not cover is worth naming too. There is no rollback procedure, no tracing hook configuration and no retry budget guidance in the README; those live in the linked Elastic documentation or in the transport package, not here.
Editorial conclusion
Adopt elasticsearch-py when you want Elastic's own transport, node discovery, connection pooling and time based penalization instead of a few lines of requests. Skip it when you need features from a server minor version newer than your client branch, since new server features need an equivalent client version, and when you are below Python 3.10. Verify first that the client branch you pin matches the server row in the README compatibility table, then confirm that the extras your code path needs, async or orjson or pyarrow, are the ones actually installed rather than inherited.
Frequently asked questions
What is elasticsearch-py used for in Python?
It is the official Python client for Elasticsearch, and it handles node discovery, persistent connections, load balancing across nodes, TLS and HTTP authentication, and translation of Python data to and from JSON. Search and index calls are passed as dictionaries rather than modelled objects.
How do I connect to Elasticsearch using Python?
The README points to the Connecting section of the Elastic getting started documentation rather than showing the connection inline. For a local cluster it gives curl -fsSL https://elastic.co/start-local | sh, which serves Elasticsearch at http://localhost:9200 and Kibana at http://localhost:5601.
Does elasticsearch-py support async Python?
Yes, through the optional async extra, which pulls in aiohttp>=3,<4. The package also depends on sniffio and anyio by default.
Which Elasticsearch version does the current elasticsearch-py client support?
Clients are forward compatible with equivalent and later minor server versions, but new server features need an equivalent client version. The README maps main to main, 9.x to 9.x, 9.x to 8.x and 8.x to 8.x, and recommends upgrading Elasticsearch before upgrading the client.
What Python versions can install elasticsearch-py?
pyproject.toml sets requires-python to >=3.10, and the classifiers list Python 3.10 through 3.14 along with CPython and PyPy. There is no supported install for anything older than 3.10.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/elastic-elasticsearch-py)Star history chart for your README
[](https://hysenlabs.com/projects/elastic-elasticsearch-py)Was this useful?