Skip to content

Python package

Python package

qgis-rs is available as a Python package that ships a Rust binary (qgis-cli) and a PyO3 extension (qgis_rs._core) — both run at native speed.

  • pip: pip install qgis-rs → import qgis_rs + qgis-cli on PATH
  • conda: conda install -c conda-forge qgis-rs → same, with optional QGIS backend

Installation

From PyPI (pip)

Terminal window
pip install qgis-rs

What you get:

  • qgis_rs Python module (pure-Rust geometry + optional QGIS rendering)
  • qgis-cli executable (Rust binary, built by maturin)
  • qgis-cli and qgis-rs console scripts (python -m qgis_rs.cli)

Pre-built wheels for Linux x86_64, macOS arm64/x86_64, Windows x86_64. If no wheel matches, pip builds from source via maturin (requires Rust ≥1.96).

From conda-forge

Terminal window
conda install -c conda-forge qgis-rs
# or
pixi add qgis-rs
mamba install -c conda-forge qgis-rs

The conda-forge package can be built in two modes:

  • Lightweight (default): No QGIS dependency. Supports info, tiles --dry-run, version, and the Python API for tile planning. This is what pip install qgis-rs gives you.
  • Full: Depends on qgis >=3.44.9. Supports render, tiles (actual rendering), export, serve. Get it via conda install -c conda-forge qgis qgis-rs.

From source (development)

Terminal window
git clone https://github.com/Archont561/qgis-rs
cd qgis-rs
# Python dev with maturin
pip install maturin
(cd py-packages/qgis-rs && maturin develop)
# Or via pixi (conda env with QGIS)
pixi install -e default
pixi run -e default py-test
# Test
python -m pytest py-packages/qgis-rs/tests -q
qgis-cli --help

Python API

from qgis_rs import Project, Extent, TilePlan, ZoomRange, Crs, plan_tiles
# Open a project (cheap — only checks path, no QGIS needed)
project = Project.open("map.qgs")
print(project.path, project.format)
info = project.info()
print(info.to_dict())
# Pure-Rust geometry — native speed, no QGIS
extent = Extent.parse("14,50,15,51")
assert extent.width() == 1.0
crs = Crs.from_epsg(3857)
print(crs.auth_id, crs.is_projected()) # EPSG:3857 True
# Tile planning — counts tiles without rendering
zooms = ZoomRange.parse("10-14")
plan = TilePlan(extent, zooms)
print(f"Would render {plan.tile_count()} tiles") # 4568
for level in plan.levels():
print(f"z={level.zoom} {level.tile_count()} tiles")
# Fast function
total, levels = plan_tiles("14,50,15,51", "10-14")
print(total) # 4568
# Rendering through the native QGIS backend
try:
rendered = project.render("output.png", width=1920, height=1080)
print(f"Wrote {rendered.path} ({rendered.bytes} bytes)")
except ValueError as e:
print(f"QGIS rendering failed: {e}")

CLI

The same Rust code powers both the standalone binary and the Python wrapper.

Terminal window
# Binary on PATH (installed via pip or conda)
qgis-cli --help
qgis-cli info map.qgs --json
qgis-cli tiles map.qgs -z 10-14 -b 14,50,15,51 -o ./tiles/ --dry-run
qgis-cli render map.qgs -o map.png --width 1920
# Via Python module (same speed — uses Rust extension directly)
python -m qgis_rs.cli info map.qgs --json
qgis-rs info map.qgs # alias

Commands

CommandPure Rust?Needs QGIS?
info✅No (basic), Yes (layers)
tiles --dry-run✅No
tiles✅Yes
render✅Yes
version✅No

Architecture

qgis-rs wheel
├── qgis_rs/
│ ├── __init__.py → the public API
│ ├── _api.py → Extent, Crs, Tile, TilePlan, ZoomRange, Project
│ ├── _transport.py → invoke(): one JSON call into Rust, errors → exceptions
│ ├── _core.so → Rust cdylib (PyO3) — exposes exactly one function, invoke()
│ └── cli.py → Python CLI wrapper (uses _api, no subprocess)
└── bin/qgis-cli → Rust binary (same logic, standalone)

There is no pure-Python fallback: every number this package returns is computed by Rust, and a second implementation would be a second set of answers. Without a built _core, importing qgis_rs raises.

  • crates/qgis-protocol — the request/response envelope and the closed list of operations
  • crates/qgis-engine — invoke(request_json) -> response_json, one arm per operation
  • crates/qgis-render — pure Rust, no QGIS needed
  • crates/qgis-cli — CLI library + binary (reused by Python package)
  • py-packages/qgis-rs — the Python distribution (client, tests, maturin config)
  • crates/qgis-py — the PyO3 adapter + qgis-cli binary it compiles

Conda-forge

See py-packages/qgis-rs/conda-recipe/ for the recipe. It builds the Rust binary and Python extension and installs both to $PREFIX/bin and site-packages.

To publish to conda-forge:

  1. Fork https://github.com/conda-forge/staged-recipes
  2. Copy py-packages/qgis-rs/conda-recipe/ to recipes/qgis-rs/
  3. Open PR

Performance

Pure-Rust ops (no QGIS):

OperationRustPython (fallback)Speedup
Extent parse0.5µs5µs10×
TilePlan 10-140.2ms3ms15×