It's worth noting that uv also supports a workflow that directly replaces pyenv, virtualenv and pip without mandating a change to a lockfile/pyproject.toml approach.
uv python pin <version> will create a .python-version file in the current directory.
uv virtualenv will download the version of Python specified in your .python-version file (like pyenv install) and create a virtualenv in the current directory called .venv using that version of Python (like pyenv exec python -m venv .venv)
uv pip install -r requirements.txt will behave the same as .venv/bin/pip install -r requirements.txt.
uv run <command> will run the command in the virtualenv and will also expose any env vars specified in a .env file (although be careful of precedence issues: https://github.com/astral-sh/uv/issues/9465)
+1, this is the exact reason I started using uv. Extremely convenient.
For some reason uv pip has been very slow, however. Unsure why, might be my org doing weird network stuff.
Doesn't it store the python version in the pyproject.toml though, is the python version file needed?
uv and its flexibility is an a absolute marvel. Where pip took 10 minutes, uv can handle it in 20-30s.