# pyproj: Python Bindings for the PROJ Coordinate Transformation Library

> pyproj is the Python interface to PROJ, the cartographic projections and coordinate transformations library, and it is a Cython binding rather than a pure Python reimplementation. The build detects the installed PROJ version, the minimum supported PROJ is 9.4.0, and the package requires Python 3.12 or newer.

**pyproj4/pyproj** — Python interface to PROJ (cartographic projections and coordinate transformations library)

- Repository: https://github.com/pyproj4/pyproj
- Website: https://pyproj4.github.io/pyproj
- Stars: 1,237 · Forks: 251
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/pyproj4-pyproj

## A Python Surface Over a C Library

pyproj is described in one line as the Python interface to PROJ, the cartographic projections and coordinate transformations library. The important word is interface.

The projection catalogue, the transformation pipelines and the geodesic calculations all live in PROJ, which is a C library, and pyproj is the layer that makes them callable from Python. The advantage is coverage and correctness, because PROJ is where the projection data and the datum transformations are maintained, and a Python project that reimplements them is taking on work that has an existing answer.

The build configuration says how the binding is made. The build system requires setuptools 77.0.1 or newer and Cython 3.1 or newer, with the setuptools build backend, and the setup.py file defines a custom Extension class that carries the CMake style arguments for the C extension. Cython is the layer that turns the Python-facing definitions into the compiled extension module, which is why the project is a mix of Python and generated C rather than pure Python.

The package metadata is current rather than conservative. It requires Python 3.12 or newer, and the classifiers list Python 3.12, 3.13, 3.14 and 3.15, along with a Typing Typed marker and a free threading classifier at 2, Beta. The only hard runtime dependency is certifi, which is there for the network data files PROJ can fetch, and there is an optional extra for shapely if you want geometry interoperability with the rest of the geospatial stack.

## How the Build Finds Your PROJ

The setup.py file is worth reading because it contains the one decision that trips people up, which is how the build knows which PROJ it is binding against.

The minimum is declared as a constant:

```python
PROJ_MIN_VERSION = (9, 4, 0)
```

Then the version is determined in two ways. The get_proj_version function prefers the PROJ_VERSION environment variable, and if that is set it is parsed and used. If it is not set, the function falls back to running the proj executable from the internal proj directory and parsing its output with a regular expression that looks for a Rel. followed by a three part version number. If neither works, the build stops with a message telling you to set the PROJ_VERSION environment variable.

So there are two supported situations. Either PROJ is installed in the tree or on the system and the proj binary can be run, or the version is declared explicitly. The fallback error is deliberate, since guessing a PROJ version would produce a binary that fails in confusing ways at run time instead of at build time.

The internal directory is named proj_dir and lives under the pyproj package directory, which is where a vendored copy of the library would sit if the build were configured to use one rather than a system installation.

## The Tooling: mypy, flake8, pre-commit and pytest

The Makefile is the entry point for the development tasks, and it is a conventional Python one with a self-documenting help target that parses the file for comments.

The type check is a target of its own, check-type, which runs mypy against the pyproj package, and the check target combines it with the pre-commit run over all files, with the show-diff-on-failure flag. The repository ships a .pylintrc and a .flake8 file as well as a flake8/ directory, and a .coveragerc for the coverage configuration.

The test targets are three. make test runs py.test, make test-verbose runs it with full verbosity and no output capture, and make test-coverage runs it with a term-missing coverage report scoped to the pyproj package. The pytest.ini at the root holds the pytest configuration, and the codecov.yml with the codecov badge is how coverage is published.

The documentation is Sphinx and has its own targets, with install-docs to fetch the requirements through the pip group syntax and a docs target that cleans and builds the HTML, plus a separate docs-browser target. The CI lives in a ci/ directory, and the pre-commit configuration at the root means the formatting and linting rules are enforced by a hook rather than by review.

## Python 3.12, Typing and the Free Threading Caveat

The Python version requirement is worth understanding as a project policy rather than an accident. The package requires 3.12 or newer, and the classifiers run up to 3.15, so the project is tracking the newest Python releases rather than supporting the long tail.

The Typing Typed classifier says the package ships type information, and the mypy target in the Makefile checks the pyproj package itself, so the typing is verified rather than asserted. For a library that sits under other people's scientific code, that matters, because a transformation pipeline with a wrong return type propagates silently.

The free threading classifier is the interesting one. It is marked 2, Beta, which is the standard scale for a package that is expected to work under free threaded Python but has not been fully validated for it. The reason to care is that a geospatial pipeline is a natural fit for parallelism, since coordinate transformations are embarrassingly parallel work, so a library that will run on a free threaded interpreter is a useful thing for a project that has threads to spare.

The repository also carries a Zenodo DOI badge, which means releases are archived with a DOI, and the LICENSE file is MIT for the binding with a separate LICENSE_proj for the PROJ library itself.

## Where the Project Answers Questions

The README is deliberately short, and it tells you where everything else lives.

There are two documentation URLs, a stable one and a latest one, and the changelog is published at the stable documentation path under a history page. The project URL in the package metadata points at the same site.

For problems there are three channels, and the separation is worth noting. Bugs and feature requests go to the GitHub issue tracker, questions go to GitHub Discussions, and the wider GIS community can be reached through the pyproj tag on GIS StackExchange. A library used by people who do not read its issue tracker is a library whose questions end up in a forum, and the third channel exists for exactly that.

The contributor list is long, and the all-contributors setup means it is generated rather than maintained by hand. The maintainer is Jeff Whitaker, with the rest of the group described as pyproj contributors, and the repository has a CODE_OF_CONDUCT.md and a CONTRIBUTING.md alongside a HOW_TO_RELEASE.md.

That release document is the one to read if you are thinking about packaging pyproj yourself, since it describes the process the maintainers use, and the process of checking the PROJ version, building the extension and publishing the wheel is the part that is easy to get subtly wrong.

## The Release Line and What Changed Recently

The recent releases are 3.8.0 on 2026-09-05, preceded by the 3.8.0 release candidate 3.8.0rc0 the same day, and 3.7.2 on 2025-08-14. The release candidate shipped first and the final release followed it, which is the pattern you would expect from a project that tests against a range of PROJ versions before cutting the final build.

The distribution channels are broad, which is the practical reason pyproj is easy to get installed. There is a PyPI package, a conda-forge package through the Anaconda Server badge, and a pre-commit hook configuration, so the project can be applied to a repository as a lint step as well as imported as a library. The CI badge is for the Tests workflow, and the coverage badge reports through codecov.

The repository is not archived and the last push was on 2026-10-15, so the work is current, and the recent version numbering with a Python 3.12 floor and the free threading classifier suggests a project that has been modernised rather than one that is being kept alive.

That is the summary for an evaluator. pyproj is a well established binding with a modern toolchain and a maintained release line, and it uses a compiled extension linked to PROJ. Published binary wheels bundle PROJ; building from source requires an appropriate PROJ installation.

## Conclusion

pyproj fits a Python geospatial project that needs the full projection and transformation catalogue of PROJ rather than a hand-rolled subset, and it is the right choice when accuracy of the transformation matters more than the purity of the dependency. It is a poor fit if you want a pure Python install with no compiled extension and no system PROJ, since the Cython layer and the PROJ library itself are both required. Before upgrading, read HOW_TO_RELEASE.md and check the PROJ version on the machine, because the setup script will fail the build when PROJ is older than 9.4.0 or when it cannot determine the version, and note that the optional shapely extra is the way to get geometry interoperability. The package is MIT licensed, the newest release is 3.8.0 from 2026-09-05, and the last push was on 2026-10-15.

## FAQ

### What is pyproj and what does it wrap?

It is the Python interface to PROJ, the cartographic projections and coordinate transformations library. The projection data and the transformations live in the C library, and pyproj is the Cython binding that exposes them to Python, with the only hard runtime dependency being certifi and an optional extra for shapely.

### Which PROJ version does pyproj need?

The minimum is 9.4.0, declared as PROJ_MIN_VERSION in setup.py. The build determines the version from the PROJ_VERSION environment variable if it is set, and otherwise by running the proj executable and parsing its version, failing the build with a message if neither works.

### Which Python versions does pyproj support?

Python 3.12 or newer, with classifiers for 3.12, 3.13, 3.14 and 3.15. The package is typed and marked as Typing Typed, and it carries a free threading classifier at level 2, Beta.

### How do I run the pyproj tests?

Through the Makefile: make test runs py.test, make test-verbose runs it with full verbosity, and make test-coverage adds a term-missing coverage report for the pyproj package. The check target runs mypy over the package and then the pre-commit hooks over all files.

### How is pyproj packaged and released?

The build uses setuptools with Cython for the extension, the version is dynamic, and the release process is documented in HOW_TO_RELEASE.md. Packages are published on PyPI and on conda-forge, the docs are built with Sphinx and published under a stable and a latest URL, and releases carry a Zenodo DOI.

## Sources

- [License: MIT](https://github.com/pyproj4/pyproj/blob/main/LICENSE)
- [Project website](https://pyproj4.github.io/pyproj)
- [pyproj4/pyproj on GitHub](https://github.com/pyproj4/pyproj)
- [README](https://github.com/pyproj4/pyproj/blob/main/README.md)
- [Releases](https://github.com/pyproj4/pyproj/releases)

---

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