# h3-py: Python Bindings for the H3 Hexagonal Geospatial Index

> h3-py wraps the H3 core library, a C implementation of a hierarchical hexagonal geospatial index, and it is careful about the boundary between the two: the Python version matches the C version in its major and minor numbers but is free to differ in the patch number, so the binding can ship fixes without a C release. The build is scikit-build-core and Cython, and CI enforces full test coverage.

**uber/h3-py** — Python bindings for H3, a hierarchical hexagonal geospatial indexing system

- Repository: https://github.com/uber/h3-py
- Website: https://uber.github.io/h3-py
- Stars: 1,045 · Forks: 148
- Language: Python
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/uber-h3-py

## A Hexagonal Index With a Python Surface

h3-py is Uber's H3 hexagonal hierarchical geospatial indexing system in Python, and it is Python bindings for the H3 core library, which is the C implementation of that index.

The index itself is a hierarchy of hexagons over the globe, with a resolution parameter that determines how the cells are subdivided, and the bindings expose that model directly. The whole API in the README is this:

```python
>>> import h3
>>> lat, lng = 37.769377, -122.388903
>>> resolution = 9
>>> h3.latlng_to_cell(lat, lng, resolution)
'89283082e73ffff'
```

A latitude and longitude become a cell identifier, and the identifier is a string. The interesting property of the design is that cells at a given resolution tile the plane exactly, with no gaps and no overlaps, which is why an index built this way is a good fit for aggregation problems and a poor fit for area-accurate shapes.

That last point is the limitation worth carrying into an evaluation. The bindings give you the index; they do not make the index the right choice for your shapes.

## Several API Families With the Same Functions

One of the more unusual choices in the library is that it offers multiple APIs, and the README is explicit about how they relate.

All APIs have the same set of functions, and the APIs differ only in their input and output formats. There is an API reference and a separate API comparison page that lays out the differences.

So the choice is not which functions you can call, it is what shape the values take. One family may take and return tuples, another may return a list, another may use a different object for a cell, and the underlying calls are the same. That matters when you are porting code between them, and it matters when you are writing generic code that should work across whichever family the user picked.

The design has a practical consequence for an application. If your code is written against one API family, moving to another is a change of types at the boundaries rather than a change of logic, which is the point of keeping the function sets identical. The comparison page is the document to read before committing to one.

## The Versioning Convention Between C and Python

The versioning section is the part of the README that a careful adopter should read, because it defines a contract between two projects that are released separately.

h3-py wraps the H3 core library, which is written in C, and the C and Python projects each employ semantic versioning, where versions take the form X.Y.Z. The h3-py version string is guaranteed to match the C library string in both major and minor numbers, but may differ on the patch number.

The stated reason is that this convention provides users with information on breaking changes and feature additions, while providing downstream bindings like this one with the versioning freedom to fix bugs. In practice, that means a patch release of h3-py can carry a fix to the binding without waiting for the C library to release, and a user reading the two numbers together knows that a difference in the patch is not a compatibility problem.

The version of both is visible at runtime:

```python
>>> import h3
>>> h3.versions()
{'c': '4.1.0', 'python': '4.1.1'}
```

So the two numbers in that dictionary are the same in major and minor, and the patch is where they are free to differ, exactly as the convention says.

## The Build Is scikit-build-core, Cython and CMake

This is a compiled extension, and the packaging reflects that. The build system requires scikit-build-core and Cython, with the scikit_build_core.build backend, and there is a CMakeLists.txt at the top of the repository plus a .gitmodules file, because the C library it wraps is a submodule rather than a vendored copy.

The package metadata is deliberately lean. The name is h3, the version is 4.5.0, the licence is Apache-2.0, and the dependencies list is empty. There is one optional dependency group, numpy, which is there for the vectorised helpers rather than for the core index.

The supported versions are listed in the classifiers, and they are current: Python 3.10 through 3.14, a Free Threading classifier, and the three operating systems, MacOS X, POSIX Linux and Windows. The C and Cython classifiers are there as well, because the compiled code is part of the package.

The development group is small, pytest with pytest-cov and numpy, and the documentation group pulls in jupyter-book, Sphinx, JupyterLab, jupyterlab-geojson, geopandas and geodatasets, which tells you the documentation is a set of executable notebooks rather than prose.

## The justfile and the Coverage Rule

The developer workflow is driven by a justfile, and the file is worth reading because it encodes the project's rules rather than just its commands.

The first rule is that the C extension is never edited in place. The file exports UV_NO_EDITABLE as 1 with the comment that editable installs are disabled so that uv sync does a full build of the C extension, and the reinstall recipe cleans the uv cache for h3 and reinstalls the package. The test recipe depends on that reinstall, and the comment says locally you always reinstall before testing. The cython test has its own recipe that cythonises the example Cython file and then runs the tests against it.

The second rule is the coverage gate. The ci-test recipe runs pytest with coverage over the library and over the test directory and with --cov-fail-under=100, and the coverage badge in the README says 100% with a comment noting that 100 percent coverage is enforced in CI.

That is a strict rule, and it explains why the justfile forces a rebuild before every test run. A stale extension would make the coverage numbers meaningless, so the build is not optional.

## Installing and the Notebook Ecosystem

There are two documented installation routes, and they differ in the package name rather than the command.

From PyPI:

```bash
pip install h3
```

And from conda, which uses the h3-py name:

```bash
conda config --add channels conda-forge
conda install h3-py
```

The naming difference is a small trap worth knowing about, because the PyPI project is called h3 while the conda package is h3-py, and a script that installs the wrong one on a conda machine will fail in a confusing way.

The examples are a separate thing again. There is a collection of example notebooks in the h3-py-notebooks repository, and the README invites contributions of examples and visualisations to it, and there is an introductory walkthrough of the API hosted through nbviewer.

So the documentation strategy is threefold: the API reference and the comparison page for reference, the notebooks for working examples, and the walkthrough for a first pass. The package also ships a docstring for the version information, and the README notes a to-do about that docstring being generated from the versions function instead of being maintained by hand, which is a small sign of where the maintainers are heading.

## Conclusion

h3-py fits a Python geospatial project that has already decided to use H3, and that wants the index in the same language as the rest of its code rather than calling a subprocess or a service. It is a poor fit if you are choosing an indexing scheme now, because the hexagonal hierarchy has properties that suit some problems and not others, and the bindings do not make those decisions for you. Before adopting it, check which of the API families matches your existing data structures, since the functions are the same and only the input and output formats differ, and note the coverage requirement in the build, because the justfile forces a rebuild of the extension before every test run and that slows down a tight loop. The package is Apache-2.0 licensed, the current version is 4.5.0, and the last push was on 2026-10-09.

## FAQ

### What is h3-py?

It is Python bindings for the H3 core library, which is Uber's hierarchical hexagonal geospatial indexing system written in C. The package exposes the index directly, so a latitude and longitude become a cell identifier at a given resolution, for example h3.latlng_to_cell returning a string like 89283082e73ffff.

### Why does h3-py offer several APIs?

All APIs have the same set of functions and differ only in their input and output formats, so choosing one is a choice of data shapes rather than of capabilities. The API reference and the API comparison page document the differences.

### How do the h3-py and H3 C library versions relate?

Both use semantic versioning, and the h3-py version string is guaranteed to match the C library string in major and minor numbers but may differ on the patch number, which gives the binding versioning freedom to fix bugs. Both numbers are visible through h3.versions(), which returns a dictionary with the c and python entries.

### How do I install h3-py?

From PyPI with `pip install h3`, or from conda with `conda config --add channels conda-forge` followed by `conda install h3-py`. The package requires Python 3.10 or newer, supports macOS, Linux and Windows, and has no runtime dependencies beyond an optional numpy extra.

### What does the test workflow of h3-py enforce?

The justfile forces a rebuild of the C extension before testing, and the CI test recipe runs pytest with coverage over the library and the test directory with --cov-fail-under=100, and the coverage badge records that 100 percent coverage is enforced in CI. There is a separate recipe that cythonises an example file and tests the Cython path.

## Sources

- [License: Apache-2.0](https://github.com/uber/h3-py/blob/master/LICENSE)
- [Project website](https://uber.github.io/h3-py)
- [README](https://github.com/uber/h3-py/blob/master/README.md)
- [Releases](https://github.com/uber/h3-py/releases)
- [uber/h3-py on GitHub](https://github.com/uber/h3-py)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/uber-h3-py
