uv and maturin

uv is a Python package and project management tool.1 Maturin is a build backend for Python extension modules implemented in Rust. How to best use them together?

Why uv?

First of all, if you’ve already got Maturin, why do you need uv? There are a couple of reasons:

  • uv takes care of the virtualenv management.
  • You can use uv to install development dependencies such as pytest.

You could use another virtualenv manager such as tox. Myself, I wanted to use uv because I’m already familiar with it and it’s fast.

Setting up the project

Start by creating a new project the usual way:

maturin new my_project

Among other things, this creates a standard pyproject.toml that works with uv. If you run now uv sync, uv will build the project using Maturin’s build backend, create a virtualenv and install the package there.

However, there is a problem: when you edit your Rust source files, there’s no easy way to rebuild the package. Running uv sync will not help because uv does not know that the Rust source files affect the package.

Running maturin develop does rebuild the package, but if you do anything with uv that triggers a sync - for example, using uv run - then it will re-install the cached old version of the package.

Below, I present three options for making it work.

Option 1: Let uv handle the rebuild

You can tell uv that the package needs to rebuild whenever the Rust source changes. To do that, you’ll need to set tool.uv.cache-keys in pyproject.toml:

[tool.uv]
cache-keys = [{file = "pyproject.toml"}, {file = "Cargo.toml"}, {file = "**/*.rs"}]

Note that even if you have a mixed Python/Rust package, the package does not need to rebuild when the Python files change. uv sync by default installs the package as editable, so the Python interpreter uses the source files directly.

There’s a downside to this approach: uv sync builds the package in the release mode which yields faster code but slower build times than the debug mode used by maturin develop.

Option 2: Use maturin develop

The alternative is to not install the project package at all when running uv sync. Edit pyproject.toml:

[tool.uv]
package = false

Now if you run uv sync, it will install dependencies but not the project itself. You need to install it manually with Maturin:

maturin develop

You will have to re-run this command whenever you change the Rust files.

Option 3: Use maturin import hook

If you want to use maturin develop, but you also want the code to rebuild automatically whenever it changes, then the Maturin import hook is the way to go. It rebuilds the code if needed when you import it in Python.

Like in option 2, you need to set tool.uv.package to false. You also need to set the minimum required Python version to 3.9 or higher - the default by maturin new is 3.8:

[project]
# ...
requires-python = ">=3.9"

Then, let’s install the import hook:

uv add --dev maturin_import_hook
uv run -m maturin_import_hook site install
maturin develop

You need to install the project with maturin develop once, but after that the import hook will take care of the rebuilds.

Gotcha: If you’re on macOS and you use Python installed with Mac Homebrew, this will not work with Maturin 0.3.0. A workaround is to use uv-managed Python: uv sync --managed-python

Managing Python dependencies

You can manage Python dependencies with the usual uv commands. For example, to add pytest as a development dependency:

uv add --dev pytest

  1. In my opinion, uv is the snappiest and the easiest-to-use package management tool for Python right now and it’s the one that I recommend to most people. I’ve previously recommended Poetry, but uv has surpassed it in features and it was always much faster. ↩︎


About the author: My name is Miikka Koskinen. I'm an experienced software engineer and consultant focused on solving problems in storing data in cloud: ingesting the data, storing it efficiently, scaling the processing, and optimizing the costs.

Could you use help with that? Get in touch at miikka@jacksnipe.fi.

Want to get these articles to your inbox? Subscribe to the newsletter: