A quick uv recipe I figured out today, for running the tests for a project against multiple Python versions.
The key command option is uv run --with-editable .[test].
Start with any Python project that has a [test] extra defined, for example:
cd /tmp
git clone https://github.com/simonw/datasette
cd datasetteThen run uv against it with a specific Python version like this:
uv run --python 3.14 --isolated --with-editable '.[test]' pytest -n autoHere I'm using --isolated to make sure nothing from any other environments sneaks in.
The --with-editable '.[test]' part is important - it tells uv to install the current project in editable mode so that changes you make will be picked up. I first tried this using --with '.[test]' and was confused as to why changes I made to the code weren't being picked up when I re-ran the tests.
Datasette uses pytest-xdist to run tests in parallel, so I added -n auto to have it automatically use all available CPU cores.
I wrote a little helper script (with ChatGPT's help) to make this easier to remember. It uses Python 3.14 by default but you can pass a different version as the -p argument. Any subsequent arguments will be passed to pytest.
#!/bin/sh
set -eu # (no pipefail in POSIX sh)
usage() {
echo "Usage: uv-test [-p|--python PY_VER] [pytest args...]"
echo " -p, --python Set Python version (default: \$UV_PY or 3.14)"
echo " -h, --help Show this help"
}
PYVER="${UV_PY:-3.14}"
# Parse only our flags; pass the rest to pytest
while [ $# -gt 0 ]; do
case "$1" in
-p|--python)
shift
[ $# -gt 0 ] || { echo "error: -p/--python requires a version" >&2; exit 2; }
PYVER="$1"; shift ;;
-h|--help) usage; exit 0 ;;
--) shift; break ;;
*) break ;;
esac
done
command -v uv >/dev/null 2>&1 || { echo "error: 'uv' not found in PATH" >&2; exit 127; }
if [ ! -f pyproject.toml ] && [ ! -f setup.py ]; then
echo "error: no project file found (need pyproject.toml or setup.py). Run from project root." >&2
exit 1
fi
exec uv run --python "$PYVER" --isolated --with-editable '.[test]' -- python -m pytest "$@"Make it executable and put it somewhere in your PATH, then you can run it like this:
uv-testOr for a specific Python version:
uv-test -p 3.13And for custom pytest arguments:
uv-test -k permissionsOr combined:
uv-test -p 3.12 -k permissions -vvFor Datasette issue #2549 I found myself needing to run the test suites for many different Datasette plugins against my local not-released main branch of Datasette. I ended up creating two new utility scripts, chmod 755 and on my path.
Here's tadd, for "test against Datasette dev":
#!/bin/sh
uv run --no-project --isolated \
--with-editable '.[test]' --with-editable ~/dev/datasette \
python -m pytest "$@"And radd, for "run against Datasette dev":
#!/usr/bin/env bash
set -euo pipefail
datasette_args=()
uv_with_args=()
# Parse CLI: take any --with X (or --with=X) for uv; everything else -> passed to datasette
while [ "$#" -gt 0 ]; do
case "$1" in
--with)
if [ "$#" -lt 2 ]; then
echo "tadd: missing value for --with" >&2
exit 2
fi
uv_with_args+=(--with "$2")
shift 2
;;
--with=*)
uv_with_args+=(--with "${1#--with=}")
shift
;;
--) # explicit separator: rest go to pytest_args verbatim
shift
# preserve original quoting/word boundaries
datasette_args+=("$@")
break
;;
*)
# anything else goes to pytest/datasette
datasette_args+=("$1")
shift
;;
esac
done
# Run
exec uv run "${uv_with_args[@]}" --no-project --isolated \
--with-editable '.[test]' --with-editable "$HOME/dev/datasette" \
-- datasette "${datasette_args[@]}"They both take arguments, e.g.:
tadd -x --pdb
radd content.db -p 8004 --rootCreated 2025-10-08T19:47:48-07:00, updated 2025-11-11T12:46:21-08:00 · History · Edit