Handling wheel size limits on PyPI for GDAL
This page answers one question: your self-contained GDAL wheel is rejected with HTTPError: 400 File too large because a vendored GDAL, PROJ, GEOS, and their data exceed PyPI’s per-file limit, so how do you shrink the wheel below the cap — or get the cap raised — without breaking portability? It sits inside the Publishing and Distributing Spatial Wheels cluster of the Modern Python Build Tooling & Wheel Configuration reference, and gives you the strip-and-exclude techniques, the data-slimming options, and the limit-increase request.
Context & Root Cause
PyPI enforces a per-file size limit — 100 MB by default — and a self-contained geospatial wheel is one of the few Python artifacts that routinely hits it. The reason is vendoring: to be portable the wheel must bundle GDAL, and GDAL drags in PROJ, GEOS, libtiff, libcurl, libsqlite3, and often libjpeg/libpng/libwebp, plus data. Two things inflate the total far beyond the code itself. First, unstripped shared libraries carry debug symbol tables that can double a library’s size. Second, PROJ’s optional datum-shift grids and GDAL’s full driver set add hundreds of megabytes of files most users never touch. The size explosion is the direct cost of the vendoring decision analysed in why vendoring PROJ causes wheel bloat.
The fix order is: cut what is dead weight (debug symbols), make heavy data opt-in (grids, drivers), and only then ask PyPI to raise the limit. Requesting an increase first is the common mistake — a wheel that ships gigabytes of grids should be slimmed, not enlarged by fiat.
Solution / Fix
This targets auditwheel 6.x, strip from binutils, GDAL 3.8.x, and PROJ 9.3.x.
1. Strip debug symbols from bundled libraries
auditwheel repair can strip while it bundles; otherwise strip the .libs/ payload after repair:
auditwheel repair --strip -w dist/ wheelhouse/*.whl # strip during repair
# or, post-hoc:
unzip -o dist/*.whl -d /tmp/w >/dev/null
strip --strip-unneeded /tmp/w/*.libs/*.so*
Stripping is safe for release wheels and is usually the single largest cut.
2. Exclude datum grids; rely on the database plus network
Ship proj.db (a few MB) but not the multi-gigabyte .tif grid set, letting PROJ fetch grids on demand:
# at import, enable on-demand grid fetching instead of bundling them
import os
os.environ.setdefault("PROJ_NETWORK", "ON")
Bundle grids only for offline/air-gapped users, as a separate extra. The packaging split is covered in bundling proj.db and datum grids in a wheel.
3. Trim GDAL’s driver set
Build GDAL with only the drivers you support rather than the full set:
# configure GDAL with a reduced driver footprint
cmake -DGDAL_ENABLE_DRIVER_ALL=OFF \
-DGDAL_ENABLE_DRIVER_GTIFF=ON -DGDAL_ENABLE_DRIVER_GPKG=ON \
-DOGR_ENABLE_DRIVER_GEOJSON=ON ...
4. If still over the cap, request an increase
Only after 1–3, open a PyPI limit-increase request for the project, citing the stripped, driver-trimmed size. Increases are granted for legitimately large native packages.
Verification
# 1. The wheel is under the per-file cap
ls -l dist/*.whl | awk '{print $5, $9}'
find dist -name '*.whl' -size +100M && echo "STILL OVER" || echo "under cap"
# expected: under cap
# 2. Stripping did not break the wheel — clean import still works
docker run --rm -v "$PWD/dist:/d" python:3.12-slim \
bash -c "pip install /d/*.whl && python -c 'from osgeo import gdal; print(gdal.__version__)'"
# expected: 3.8.x
# 3. A transform still resolves via database + network (no bundled grids)
docker run --rm -v "$PWD/dist:/d" python:3.12-slim bash -c \
"pip install /d/*.whl && PROJ_NETWORK=ON python -c 'import pyproj; print(pyproj.Transformer.from_crs(4326,3857).transform(52,5))'"
# expected: a coordinate pair
Under the cap, a clean import, and a working transform confirm the wheel shrank without losing function.
Pitfalls & Alternatives
Stripping the extension too aggressively. strip without --strip-unneeded can remove the dynamic symbols the loader needs and break import. Use --strip-unneeded (or let auditwheel --strip handle it), and re-run the smoke test in smoke-testing GDAL wheels in a clean container.
Dropping grids without a fallback. Excluding the datum grids and disabling PROJ_NETWORK leaves high-accuracy transforms failing silently for some datums. Enable network fetching or offer an offline extra.
Requesting a limit increase first. A wheel that is 400 MB because it ships every grid and driver should be slimmed, not blessed. Exhaust strip-and-exclude before asking for more headroom.
Related
- Publishing and Distributing Spatial Wheels — the parent guide on the release flow and where the size guard sits.
- Why vendoring PROJ causes wheel bloat — the root cause of the size the limit constrains.
- Bundling proj.db and datum grids in a wheel — the data-packaging split that keeps grids opt-in.