Skip to content

feat(python): add geolibre-wasm Python wrapper#10

Merged
giswqs merged 5 commits into
mainfrom
feat/python-wrapper
Jun 20, 2026
Merged

feat(python): add geolibre-wasm Python wrapper#10
giswqs merged 5 commits into
mainfrom
feat/python-wrapper

Conversation

@giswqs

@giswqs giswqs commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Scaffold a Python package under python/ (distribution geolibre-wasm, import geolibre_wasm) that runs the same WASI tool runner in-process via wasmtime, mirroring the JS geolibre-wasm/tools API.
  • API: list_tools(), list_manifests(), run_tool(tool, args=, input=) returning ToolResult(exit_code, stdout, files). Inputs are passed as bytes under /work; new files (including nested trees like raster_to_tiles output) come back as bytes.
  • Runtime geolibre-cli.wasm is resolved from an explicit wasm_path, the GEOLIBRE_WASM env var, or a cached download of the matching GitHub release asset (verified the v0.4.0 asset URL returns 200).
  • Named geolibre-wasm to match the npm package and avoid clashing with the separate geolibre application package.

Test plan

  • pytest python/tests green (list_tools, provenance, GeoParquet write->read roundtrip)
  • Verified slope, reproject_raster, render_raster_png (valid PNG), and raster_to_tiles (nested tiles/z/x/y.png keys returned)
  • python -m build --wheel produces geolibre_wasm-0.4.0-py3-none-any.whl
  • (Optional follow-up) wire a Python CI job and PyPI Trusted Publishing into the release workflow

Summary by CodeRabbit

  • New Features

    • Python package (geolibre-wasm) now available, enabling users to run GeoLibre geospatial tools via WebAssembly with no native dependencies or GDAL installation required.
  • Documentation

    • Added comprehensive Python package documentation covering installation, runtime management, and practical usage examples.
    • Updated main README with new "Use from Python" section pointing to Python package guide.

Scaffold a Python package (python/, distribution geolibre-wasm, import
geolibre_wasm) that runs the same WASI tool runner in-process via wasmtime,
mirroring the JS tools API: list_tools, list_manifests, run_tool returning a
ToolResult(exit_code, stdout, files). Inputs go in as bytes under /work and new
files (including nested trees like raster_to_tiles output) come back as bytes.

The runtime geolibre-cli.wasm is resolved from an explicit path, the
GEOLIBRE_WASM env var, or a cached download of the matching GitHub release
asset. Named geolibre-wasm to match the npm package and avoid clashing with the
separate geolibre application package.
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@giswqs, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 44 seconds. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3fc3883e-f0c8-4fe0-b56a-15759ab7f84b

📥 Commits

Reviewing files that changed from the base of the PR and between 27ce5a5 and 876d0f6.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • README.md
  • python/README.md
  • python/pyproject.toml
  • python/src/geolibre_wasm/__init__.py
  • python/src/geolibre_wasm/_core.py
📝 Walkthrough

Walkthrough

A new Python package geolibre-wasm is added under python/, implementing an in-process WASI runner for geolibre-cli.wasm via wasmtime-py. It provides list_tools, list_manifests, and run_tool APIs mirroring the JavaScript interface, with lazy runtime download/caching, a ToolResult dataclass, smoke tests, and documentation.

Changes

Python geolibre-wasm WASI wrapper

Layer / File(s) Summary
Package scaffold and public contracts
python/pyproject.toml, python/src/geolibre_wasm/__init__.py, python/src/geolibre_wasm/_core.py
pyproject.toml configures the hatchling build with wasmtime>=20 dependency; __init__.py re-exports public symbols and sets __version__ = "0.4.0"; _core.py defines RUNTIME_VERSION, PathLike, and the ToolResult dataclass.
Runtime resolution and module loading
python/src/geolibre_wasm/_core.py
Implements a shared wasmtime.Engine singleton, in-memory module cache, _cache_path(), download_runtime() with atomic temp-file replacement, runtime_path() resolving via argument → env var → cached download, and _load_module().
WASI execution engine and public API
python/src/geolibre_wasm/_core.py
_exec() writes input bytes to a temp /work dir, configures WASI argv and preopened dirs, captures stdout+stderr, calls module _start, converts ExitTrap to exit code, and collects output files. list_tools, list_manifests, and run_tool wrap _exec with command-specific parsing.
Smoke tests
python/tests/test_smoke.py
Three integration tests against the real WASI runner: tool list size/membership, manifest provenance fields, and a GeoParquet write→read roundtrip asserting PAR1 header and two-feature output.
Documentation and build ignores
.gitignore, README.md, python/README.md
Adds Python build artifact patterns to .gitignore; adds a "Use from Python" section to root README.md; adds full python/README.md with install, runtime config, usage examples, and API reference.

Sequence Diagram(s)

sequenceDiagram
  participant Caller as Python Caller
  participant run_tool as run_tool()
  participant runtime_path as runtime_path()
  participant download_runtime as download_runtime()
  participant _exec as _exec()
  participant wasmtime as wasmtime (WASI)

  Caller->>run_tool: run_tool(tool, args, input_files)
  run_tool->>_exec: _exec([tool, *args], input_files, wasm_path)
  _exec->>runtime_path: resolve effective wasm path
  alt GEOLIBRE_WASM set or arg provided
    runtime_path-->>_exec: explicit path
  else cache miss
    runtime_path->>download_runtime: fetch versioned release asset
    download_runtime-->>runtime_path: cached .wasm path
    runtime_path-->>_exec: cached path
  end
  _exec->>wasmtime: write inputs to tmpdir /work, configure WASI, call _start
  wasmtime-->>_exec: ExitTrap(code) or normal exit
  _exec->>_exec: read capture file, collect output files
  _exec-->>run_tool: ToolResult(exit_code, stdout, files)
  run_tool-->>Caller: ToolResult
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 A WASM hop from Python land,
No GDAL needed, quite as planned —
The runner spins in-memory tight,
With temp /work dirs keeping right.
run_tool, list_tools, a tidy bow,
One pip install and off we go! 🌍

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(python): add geolibre-wasm Python wrapper' accurately and concisely summarizes the main change: introducing a new Python package that wraps the geolibre WASI tool runner.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/python-wrapper

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

CI: after building the WASM, install the python package and run pytest against
the freshly built geolibre-cli.wasm (via GEOLIBRE_WASM), so the wrapper is
covered without a network download.

Release: sync the Python package version (and the runtime version it downloads)
to the tag, build a wheel + sdist, publish to PyPI via Trusted Publishing (OIDC,
no token), and attach the wheel/sdist to the GitHub release. Requires a PyPI
trusted publisher for "geolibre-wasm" (configure as a pending publisher before
the first release).
@giswqs

giswqs commented Jun 20, 2026

Copy link
Copy Markdown
Member Author

Wired the Python package into CI and the release pipeline (commit 38fdc83):

  • CI (ci.yml): after build.sh, the job installs the package and runs pytest python/tests against the freshly built geolibre-cli.wasm (via GEOLIBRE_WASM), so no network download is needed. Verified locally in a clean venv: 3 passed.
  • Release (release.yml): on a v* tag it syncs the Python package version and the runtime version it downloads to the tag, builds a wheel + sdist, publishes to PyPI via Trusted Publishing (OIDC, no token), and attaches the wheel/sdist to the GitHub release.

One-time manual setup required before the first PyPI release: configure a PyPI Trusted Publisher (a "pending publisher" since the project is new) for project geolibre-wasm, owner opengeos, repo geolibre-rust, workflow release.yml (no environment). Until that exists, the PyPI publish step will fail; everything else in the release is unaffected.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/pyproject.toml`:
- Line 20: The wasmtime version constraint in the dependencies field is too
loose, allowing versions spanning 25+ major versions which risks breaking
changes. Tighten the constraint in the dependencies line by either specifying a
minimum compatible version with an upper bound like wasmtime>=20,<50 or
specifying the latest tested version with wasmtime>=45.0.0. Choose the
constraint based on which versions have been confirmed compatible with your
codebase.

In `@python/src/geolibre_wasm/__init__.py`:
- Around line 30-38: The __all__ list in the module exports is not sorted
alphabetically, which reduces maintainability. Sort all the items in the __all__
list (ToolResult, RUNTIME_VERSION, list_tools, list_manifests, run_tool,
runtime_path, download_runtime) in alphabetical order to improve code
organization and make it easier to identify duplicates or missing exports.

In `@python/src/geolibre_wasm/_core.py`:
- Around line 128-131: In the loop that iterates over inputs.items() where
dest.write_bytes() is called, remove the unnecessary bytes() conversion wrapper
since the data variable is already typed as bytes according to the inputs
parameter type hint Mapping[str, bytes]. Change dest.write_bytes(bytes(data)) to
dest.write_bytes(data) to simplify the code and improve clarity.
- Around line 200-220: The parameter name `input` in the `run_tool` function
shadows the Python builtin `input()` function, which is flagged by static
analysis. Rename the `input` parameter to `inputs` in the function signature to
match the parameter name expected by the `_exec()` call, and update the function
call from `inputs=input` to `inputs=inputs`.
- Around line 69-84: The download_runtime function needs to add a timeout to
prevent indefinite hangs and integrity verification to prevent silent
corruption. Replace the urllib.request.urlretrieve() call with
urllib.request.urlopen() with an explicit timeout parameter (e.g., 30 seconds),
then read and write the file content manually. After writing the file, add basic
integrity verification by reading the downloaded file and checking for the WASM
magic bytes signature (\0asm) at the start of the file before replacing the
temporary file with the target file. If verification fails, raise an exception
with a clear error message describing the integrity check failure.
- Line 19: The import statement on line 19 is using the legacy type hint syntax
from Python 3.8 and earlier. Modernize this by updating the import to remove
Dict and List from the typing import, and add Mapping and Sequence to be
imported from collections.abc instead. Then update all type annotations
throughout the file by replacing List[...] with list[...], Dict[...] with
dict[...], and any usages of Mapping and Sequence to reference the
collections.abc imports. This modernizes the code to use Python 3.9+ syntax
which is more concise and the recommended approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 993fa8c8-2338-46d0-883a-3b23406c20d8

📥 Commits

Reviewing files that changed from the base of the PR and between aa0dbeb and 27ce5a5.

📒 Files selected for processing (7)
  • .gitignore
  • README.md
  • python/README.md
  • python/pyproject.toml
  • python/src/geolibre_wasm/__init__.py
  • python/src/geolibre_wasm/_core.py
  • python/tests/test_smoke.py

Comment thread python/pyproject.toml Outdated
Comment thread python/src/geolibre_wasm/__init__.py
Comment thread python/src/geolibre_wasm/_core.py Outdated
Comment thread python/src/geolibre_wasm/_core.py
Comment thread python/src/geolibre_wasm/_core.py Outdated
Comment thread python/src/geolibre_wasm/_core.py
giswqs added 2 commits June 19, 2026 21:23
Add a PyPI version badge and `pip install geolibre-wasm` / `npm install
geolibre-wasm` to the README, and note the package is usable from both
JavaScript and Python. Drop the now-inaccurate "no Python" from the intro
(there is a Python binding) in favor of "no GDAL".
- download_runtime: use urlopen with a timeout and reject non-WASM payloads
  (check the "\0asm" magic) so a hung or error-page download fails loudly
  instead of producing a cryptic module-load error.
- Tighten the wasmtime dependency to >=20,<50 to avoid surprise major bumps.
- Modernize type hints: built-in dict/list and collections.abc Mapping/Sequence
  (the module already uses `from __future__ import annotations`).
- Drop the redundant bytes() conversion when writing input files.
- Sort __all__ alphabetically.
Both Python examples now spell out that paths in `args` refer to the tool's
/work sandbox (not the host disk), that `input` files land at /work/<name>, and
that `res.files` keys are relative to /work, with an `assert res.exit_code == 0`
to surface tool errors. Prevents the common mistake of passing host paths like
/content/dem.tif (e.g. on Colab) into args. Verified the snippet runs end to end.
@giswqs giswqs merged commit 33e6f42 into main Jun 20, 2026
2 checks passed
@giswqs giswqs deleted the feat/python-wrapper branch June 20, 2026 01:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant