Back to Osrm Backend

Python Bindings Development Guide

docs/python/development.md

26.5.010.9 KB
Original Source

Python Bindings Development Guide

Installing for production

Pre-built wheels are published to PyPI for Linux (x86_64), macOS (x86_64), and Windows (amd64). They use the CPython 3.12 stable ABI (cp312-abi3) and therefore install on Python 3.12+:

bash
pip install osrm-bindings

The package itself supports Python 3.10+ when built from source — needed for 3.10/3.11, aarch64 Linux, arm64 macOS, or any platform without a pre-built wheel:

bash
pip install osrm-bindings --no-binary osrm-bindings

Source builds compile the full OSRM C++ library — this takes a long time. See platform-specific notes for prerequisites.

Installing for development

Clone the repo and install in editable mode with dev dependencies:

bash
git clone https://github.com/Project-OSRM/osrm-backend
cd osrm-backend
pip install -e ".[dev]"

Install pre-commit hooks:

bash
pre-commit install

Platform-specific build requirements

Linux

CI wheel builds run inside a custom manylinux image (nilsnolde/manylinux, branch osrm_python) that ships vcpkg pre-bootstrapped at the SHA pinned in vcpkg-configuration.json, plus a pre-warmed vcpkg binary cache compiled against this repo's vcpkg.json. The wheel build's own vcpkg install hits that cache instead of recompiling boost/tbb/etc. from source.

The image needs rebuilding when this repo's vcpkg.json, the baseline SHA in vcpkg-configuration.json, or any file under vcpkg-overlay-ports/ changes — otherwise the wheel build either misses the cache (slow) or fails on a missing port. The manylinux repo's Build workflow takes an osrmRef input for that purpose; see its README.

For local source builds outside the manylinux image, install vcpkg yourself, point CMake at its toolchain, and use the release-only triplet to match the cache:

bash
git clone https://github.com/microsoft/vcpkg
./vcpkg/bootstrap-vcpkg.sh
export VCPKG_ROOT=$PWD/vcpkg
export CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux-release"

macOS

Install OSRM's C++ dependencies via Homebrew (the same set the cibuildwheel macOS before-all uses; all ship CMake config files so the find_package(... CONFIG REQUIRED) calls in CMakeLists.txt resolve without a toolchain file):

bash
brew install lua tbb [email protected] fmt rapidjson sol2 flatbuffers \
             protozero libosmium
brew link [email protected]

Windows

Windows uses vcpkg in manifest mode for OSRM's C++ dependencies. Clone and bootstrap it, then export VCPKG_ROOT:

powershell
git clone https://github.com/microsoft/vcpkg
.\vcpkg\bootstrap-vcpkg.bat
$env:VCPKG_ROOT = "$PWD\vcpkg"

Pass the toolchain to CMake at build time via CMAKE_ARGS (see below).

Building locally

A standard pip install -e . works, but by default pip uses PEP 517 isolated builds — each invocation creates a temporary directory, compiles everything, then discards it. This means OSRM is recompiled from scratch every time.

Use --no-build-isolation to make scikit-build-core reuse the persistent build directory (build/{wheel_tag}/) across runs:

bash
# Linux / macOS
pip install -e . --no-build-isolation

# Windows (PowerShell) — VCPKG_ROOT must be set, see Platform-specific
# build requirements
$env:CMAKE_ARGS = "-DCMAKE_TOOLCHAIN_FILE=$env:VCPKG_ROOT\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-static-md"
pip install -e . --no-build-isolation

The first run is slow (full OSRM compile). Subsequent runs only recompile changed binding files.

::: warning Keep config flags identical across runs scikit-build-core hashes its configuration to detect changes. If the flags differ between runs, it wipes the build directory and starts from scratch. :::

::: warning Generator mismatch CMake records the generator in CMakeCache.txt. If you ever see Does not match the generator used previously, delete the build directory and rebuild from scratch:

powershell
Remove-Item -Recurse -Force build/cp312-abi3-win_amd64

:::

Building a wheel

After the editable install has compiled everything, produce a wheel without recompiling:

bash
# Linux / macOS
pip wheel . --no-build-isolation -w dist

# Windows (PowerShell) — uses the same CMAKE_ARGS as the editable install above
pip wheel . --no-build-isolation -w dist

CMake finds the existing artifacts in the build directory and skips recompilation. The wheel lands in dist/.

Wheel repair

Locally built wheels link against system shared libraries and are tagged as linux_x86_64 (not manylinux). To make them portable or to inspect their dependencies, use the platform-specific repair tools:

Linuxauditwheel:

bash
pip install auditwheel
auditwheel show dist/*.whl          # inspect shared library dependencies
auditwheel repair -w dist dist/*.whl # bundle libs and retag as manylinux

macOSdelocate:

bash
pip install delocate
delocate-listdeps dist/*.whl        # inspect dependencies
delocate-wheel -w dist dist/*.whl   # bundle dylibs

Windowsdelvewheel:

bash
pip install delvewheel
delvewheel show dist/*.whl          # inspect dependencies
delvewheel repair -w dist dist/*.whl

On Windows, vcpkg's shared DLLs (tbb12.dll, hwloc.dll — TBB is shared even under the static-md triplet) live in build\<wheel-tag>\vcpkg_installed\x64-windows-static-md\bin\. Pass that to delvewheel via --add-path so it can resolve and bundle them:

powershell
delvewheel repair --analyze-existing-exes `
    --add-path build\cp312-abi3-win_amd64\vcpkg_installed\x64-windows-static-md\bin `
    --add-dll hwloc.dll --no-mangle tbb12.dll --no-mangle hwloc.dll `
    -w dist dist\*.whl

::: tip cibuildwheel runs wheel repair automatically in CI. You only need these commands when building wheels locally for distribution. :::

Compiler cache

On Linux and macOS, ccache is used automatically (pre-installed in the manylinux image; installed via Homebrew for macOS CI).

On Windows, scikit-build-core defaults to the Visual Studio generator, which does not support CMAKE_CXX_COMPILER_LAUNCHER. The build dir reuse from --no-build-isolation is the main speed optimisation for local Windows development.

Running tests

Build the test data (requires the package to be installed so the osrm executables are available):

bash
# Linux / macOS
cd test/data && make

# Windows
cd test\data && windows-build-test-data.bat

Load the shared memory datastore:

bash
python -m osrm datastore test/data/ch/monaco

Run the test suite:

bash
pytest test/python/

Running cibuildwheel locally

cibuildwheel builds wheels inside isolated environments that closely match CI. Install it with:

bash
pip install cibuildwheel

Build for the current platform:

bash
cibuildwheel --platform linux    # requires Docker on non-Linux hosts
cibuildwheel --platform macos
cibuildwheel --platform windows

Wheels land in wheelhouse/.

Windows note: the toolchain wiring (CMAKE_TOOLCHAIN_FILE, VCPKG_TARGET_TRIPLET) lives in [tool.cibuildwheel.windows].environment in pyproject.toml, where $VCPKG_ROOT is expanded at build time from the host environment. Make sure VCPKG_ROOT is set in your shell before invoking cibuildwheel.

Linux note: the wheel build inside the manylinux container reads VCPKG_ROOT and VCPKG_DEFAULT_BINARY_CACHE from the image's ENV, so no toolchain forwarding is needed from the host. If you override CIBW_ENVIRONMENT_LINUX to mount a host ccache, remember it replaces (not merges with) [tool.cibuildwheel.linux].environment in pyproject.toml — re-include LD_LIBRARY_PATH and the CMAKE_ARGS line verbatim:

bash
CIBW_CONTAINER_ENGINE="docker; create_args: --volume /tmp/ccache:/ccache" \
CIBW_ENVIRONMENT_LINUX='LD_LIBRARY_PATH=/usr/local/lib64:${LD_LIBRARY_PATH} CCACHE_DIR=/ccache CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-linux-release"' \
cibuildwheel --platform linux

Type stubs

src/python/osrm/osrm_ext.pyi is auto-generated by nanobind_add_stub() at build time and committed to the repository so documentation tools can work without compiling the extension.

After changing C++ bindings, rebuild and commit the updated stub:

bash
pip install -e . --no-build-isolation   # regenerates the .pyi
git add src/python/osrm/osrm_ext.pyi

To regenerate manually without a full rebuild:

bash
pip install nanobind ruff
python -m nanobind.stubgen -m osrm.osrm_ext -o src/python/osrm/osrm_ext.pyi
ruff format src/python/osrm/osrm_ext.pyi

Releasing

Releases are driven by the monthly release workflow (.github/workflows/release-monthly.yml), not by pushing a tag by hand. The workflow bumps the version, creates the tag, drives CI, downloads the built wheels, and publishes to both PyPI and npm in one shot.

Scheduled monthly release

A cron on the 1st of each month at 08:00 UTC runs the workflow against master:

  1. Compute the next version as (YYYY-2000).M.patchlevel (e.g. 26.4.0).
  2. Bump package.json + package-lock.json, commit, create annotated tag v<version>, push branch and tag.
  3. Dispatch osrm-backend.yml on the tag. That run builds wheels + sdist via cibuildwheel and uploads them as wheels-* artifacts.
  4. Wait for the dispatched CI run to finish with conclusion success.
  5. Run the publish job: download every wheels-* artifact into dist/, publish to PyPI via trusted publisher (OIDC), then npm publish.

If PyPI fails, the npm publish still runs (the npm steps have if: ${{ !cancelled() }}), and the overall job is marked failed so the PyPI problem stays visible.

Manual release

Trigger the workflow from the Actions UI or gh workflow run release-monthly.yml with optional inputs:

  • version_override — set the version explicitly (e.g. 26.4.1) instead of using the (YYYY-2000).M.patchlevel calculation.
  • branch — release from a branch other than master.

Verification

After the run finishes, check:

Version mechanics

pyproject.toml uses setuptools-scm with local_scheme = "no-local-version". On a tag checkout (e.g. v26.4.0), the Python version resolves cleanly to 26.4.0, matching the package.json version that release-monthly.yml committed when creating the tag.