gratisgis (Python client)
Talk to a GratisGIS portal from Python: notebooks, scheduled scripts, CI, or an editor on your own machine.
pip install gratisgis
The full guide, with worked examples for filtering, buffering, writing
a new layer, and exporting, is in the portal at
/help/reference/python-client.
Releases go out on a python-v* tag through
.github/workflows/publish-python.yml, using PyPI trusted publishing,
so there is no API token anywhere. Bump __version__ in
src/gratisgis/client.py (the single source; pyproject.toml reads it)
and tag to match, or the workflow refuses the release.
Get a key
In the portal: Profile -> API keys -> Create key. The token is shown once. A key acts as the user who created it, so it sees exactly what you see, with the same sharing and geographic limits. Tick Read only unless the script needs to write.
Use it
from gratisgis import GratisGIS
gg = GratisGIS("https://your-portal.org", api_key="ggk_...")
# Which account is this key?
print(gg.whoami()["username"])
# Find a layer and read it as GeoJSON
layer = gg.find_items(type="data_layer", query="parcels")[0]
fc = gg.read_features(layer["id"], "parcels", limit=500)
print(len(fc["features"]), "features")
Prefer environment variables so the secret stays out of your source, which also means the same script runs unchanged anywhere:
# GRATISGIS_URL=https://your-portal.org
# GRATISGIS_API_KEY=ggk_...
gg = GratisGIS.from_env()
Writing
features = [
{
"type": "Feature",
"geometry": {"type": "Point", "coordinates": [-79.85, 38.93]},
"properties": {"name": "New hydrant", "status": "active"},
}
]
gg.add_features(layer_id, "hydrants", features)
add_features batches for you. The portal refuses more than 5000
features in one request, so hand it a list or a generator of any size
and it splits the work.
Edits are recorded as new observations rather than overwriting rows, so a feature's history stays intact and remains queryable:
gg.update_feature(layer_id, "hydrants", feature_id,
properties={"status": "needs service"})
A monthly refresh, end to end
The shape most people want. Point it at a source, replace what changed, and let cron run it:
import os
from gratisgis import GratisGIS, PortalError
def main() -> int:
gg = GratisGIS.from_env()
layer_id = os.environ["PARCEL_LAYER_ID"]
try:
incoming = fetch_from_county() # your code
result = gg.add_features(layer_id, "parcels", incoming)
print(f"appended {result['appended']} features")
return 0
except PortalError as exc:
# Non-zero so cron / systemd notices, and the portal's own
# message reaches your logs verbatim.
print(f"refresh failed: {exc}")
return 1
if __name__ == "__main__":
raise SystemExit(main())
Errors
Every failure raises a subclass of PortalError carrying the portal's
own message, plus the status and route:
| Class | When |
|---|---|
AuthError |
401 / 403. Bad, expired, or revoked key; read-only key attempting a write; a key used on an admin route |
NotFoundError |
404. Missing, or not visible to this key |
ValidationError |
400 / 422. Understood and refused |
ConflictError |
409. Something moved under you |
RateLimitError |
429. Carries retry_after when the portal sends it |
PortalError |
Anything else, including a network failure |
What this does not do
It wraps the item API and the feature endpoints, which is what automation needs. It is not a mirror of every portal route.
It also has exactly one dependency, httpx. GeoPandas, Shapely and
friends belong to your environment, not to a portal client: bring the
stack you already use and hand this GeoJSON.
There is no hosted execution environment. Scripts run wherever you run them. Server-side scheduled execution is tracked separately.
Development
pip install -e ".[dev]"
pytest
The tests run against httpx.MockTransport, so no portal is needed.
Metadata
Release files for gratisgis 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gratisgis-0.7.0.tar.gz | 33.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gratisgis-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 55.7 kB
Release files / gratisgis-0.7.0.tar.gz
| Download URL | gratisgis-0.7.0.tar.gz |
|---|---|
| Size | 33.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34e67b2c54bc2f79aa7a52cbda64543697442aff56f0f680badab0504b119fa1
|
|
BLAKE2b-256 checksum How to use checksums |
775217ce2ea7b9ebf6704e55b57047d3b4dbe10d5644503c755af2ed366ecf84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency logRelease files / gratisgis-0.7.0-py3-none-any.whl
| Download URL | gratisgis-0.7.0-py3-none-any.whl |
|---|---|
| Size | 22.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d454b548d7dbb9a7676cef39fad75304e058640943181fa149a8731dae99ae78
|
|
BLAKE2b-256 checksum How to use checksums |
8c08869c0abe8eb1e8f5ba2fc81e5ca2765daebb2a7a5c101f411489804a6b28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 9, 2026.
Transparency log