I'm having trouble figuring out the correct way to use uv to replace my current use of pipenv.
See section at bottom, part of this was because of CONDA_PREFIX but I still have questions - also note that the Conda gotcha that caught me out here has been addressed in uv 0.5.
Here's the pattern I use. I start by creating a new project using a cookiecutter template:
cookiecutter gh:simonw/click-app [1/6] app_name (): demo-app
[2/6] description (): Demo
[3/6] hyphenated (demo-app):
[4/6] underscored (demo_app):
[5/6] github_username (): simonw
[6/6] author_name (): Simon Willison
Now I have this:
find demo-appdemo-app
demo-app/LICENSE
demo-app/pyproject.toml
demo-app/tests
demo-app/tests/test_demo_app.py
demo-app/README.md
demo-app/.gitignore
demo-app/.github
demo-app/.github/workflows
demo-app/.github/workflows/publish.yml
demo-app/.github/workflows/test.yml
demo-app/demo_app
demo-app/demo_app/__init__.py
demo-app/demo_app/cli.py
demo-app/demo_app/__main__.py
I want to start hacking on demo-app using a dedicated virtual environment and pip install -e '.[test]' to start work. I currently do this:
cd demo-app
pipenv shell
# Now I'm in a fresh, activated virtual environment
pip install -e '.[test]'That's installed my app itself in editable mode plus its test dependencies, which includes pytest. So now I can run:
python -m pytestAnd see:
============================= test session starts ==============================
platform darwin -- Python 3.10.10, pytest-8.3.3, pluggy-1.5.0
rootdir: /private/tmp/demo-app
configfile: pyproject.toml
collected 1 item
tests/test_demo_app.py . [100%]
============================== 1 passed in 0.01s ===============================
I can also run my new demo-app directly, since it's been added to my pipenv environment as a command:
demo-app --helpUsage: demo-app [OPTIONS] COMMAND [ARGS]...
Demo
Options:
--version Show the version and exit.
--help Show this message and exit.
Commands:
command Command description goes here
And confirm the location of that demo-app executable with which:
which demo-app/Users/simon/.local/share/virtualenvs/demo-app-AU_yVo5p/bin/demo-app
I can't figure out the right uv pattern for this. Ideally I'd like to avoid the "activate your virtual environment" step entirely and do everything with uv run.
Here's what I've tried that didn't work:
uvx cookiecutter gh:simonw/click-appThis works the same as above, I go through the same steps to create demo-app.
cd demo-app
uv run pytestThis doesn't work:
Using CPython 3.11.1
Creating virtual environment at: .venv
Installed 1 package in 2ms
============================= test session starts ==============================
platform darwin -- Python 3.10.10, pytest-8.3.2, pluggy-1.5.0
rootdir: /private/tmp/b/demo-app
configfile: pyproject.toml
plugins: icdiff-0.6, syrupy-4.5.0, asyncio-0.21.1, django-4.9.0, clarity-1.0.1, httpx-0.22.0, anyio-3.7.1, requests-mock-1.11.0, timeout-2.3.1, xdist-3.6.1
asyncio: mode=strict
collected 0 items / 1 error
==================================== ERRORS ====================================
___________________ ERROR collecting tests/test_demo_app.py ____________________
ImportError while importing test module '/private/tmp/b/demo-app/tests/test_demo_app.py'.
Hint: make sure your test modules/packages have valid Python names.
Traceback:
/opt/homebrew/Caskroom/miniconda/base/lib/python3.10/importlib/__init__.py:126: in import_module
return _bootstrap._gcd_import(name[level:], package, level)
tests/test_demo_app.py:2: in <module>
from demo_app.cli import cli
E ModuleNotFoundError: No module named 'demo_app'
=========================== short test summary info ============================
ERROR tests/test_demo_app.py
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
=============================== 1 error in 0.07s ===============================
Clearly I need to do the equivalent of pip install -e '.[test]' - but I can't figure out what that equivalent is!
For a moment I thought I had figured it out:
uv pip install -e '.[test]'And now:
uv run pytest================================================= test session starts ==================================================
platform darwin -- Python 3.10.10, pytest-8.3.2, pluggy-1.5.0
rootdir: /private/tmp/b/demo-app
configfile: pyproject.toml
plugins: icdiff-0.6, syrupy-4.5.0, asyncio-0.21.1, django-4.9.0, clarity-1.0.1, httpx-0.22.0, anyio-3.7.1, requests-mock-1.11.0, timeout-2.3.1, xdist-3.6.1
asyncio: mode=strict
collected 1 item
tests/test_demo_app.py . [100%]
================================================== 1 passed in 0.01s ===================================================
But no... that's not right. I think it's a coincidence that that worked, because:
uv run which pytest/opt/homebrew/Caskroom/miniconda/base/bin/pytest
I don't want to run the globally installed pytest - I want to run a pytest that lives in my current virtual environment.
ls .venv/binactivate
activate.bat
activate.csh
activate.fish
activate.nu
activate.ps1
activate_this.py
deactivate.bat
pydoc.bat
python
python3
python3.11
There's no pytest in there - so my uv pip install -e '.[test]' didn't install my test dependencies (from pyproject.toml) as expected.
I even tried this:
uv pip install pytestBut when I ran this I got an error:
uv run python -m pytest/private/tmp/b/demo-app/.venv/bin/python3: No module named pytest
And even this returns no matches:
find .venv | grep pytestSo I seem to be missing something pretty fundamental here - how come uv pip install pytest didn't install pytest into the .venv directory there?
For the record, the pyproject.toml file in the project looks like this:
[project]
name = "demo-app"
version = "0.1"
description = "Demo"
readme = "README.md"
authors = [{name = "Simon Willison"}]
license = {text = "Apache-2.0"}
requires-python = ">=3.8"
classifiers = [
"License :: OSI Approved :: Apache Software License"
]
dependencies = [
"click"
]
[project.urls]
Homepage = "https://github.com/simonw/demo-app"
Changelog = "https://github.com/simonw/demo-app/releases"
Issues = "https://github.com/simonw/demo-app/issues"
CI = "https://github.com/simonw/demo-app/actions"
[project.scripts]
demo-app = "demo_app.cli:cli"
[project.optional-dependencies]
test = ["pytest"]Since this is meant to be a CLI utility, I want to be able to do this:
uv run demo-app --helpTo exercise the app as if it had been installed.
This appeared to work...
Usage: demo-app [OPTIONS] COMMAND [ARGS]...
Demo
Options:
--version Show the version and exit.
--help Show this message and exit.
Commands:
command Command description goes here
But then I ran this command:
uv run which demo-appAnd got back:
/opt/homebrew/Caskroom/miniconda/base/bin/demo-app
I had intended to install demo-app in my current .venv/ folder managed by uv, but it looks like I accidentally installed it globally instead!
I ran this:
env | grep CONDA_PREFIXAnd got:
CONDA_PREFIX=/opt/homebrew/Caskroom/miniconda/base
https://docs.astral.sh/uv/pip/environments/#discovery-of-python-environments says:
When running a command that mutates an environment such as
uv pip syncoruv pip install, uv will search for a virtual environment in the following order:
- An activated virtual environment based on the
VIRTUAL_ENVenvironment variable.- An activated Conda environment based on the
CONDA_PREFIXenvironment variable.- A virtual environment at
.venvin the current directory, or in the nearest parent directory.
So that's probably why it's going wrong for me!
Trying this again:
cd /tmp
mkdir noc
cd noc
unset CONDA_PREFIX
uvx cookiecutter gh:simonw/click-app
# Go through the steps
cd demo
uv pip install -e '.[test]'
uv run pytestThat worked!
And I can run this:
uv run python -m demoUsage: python -m demo [OPTIONS] COMMAND [ARGS]...
Demo
Options:
--version Show the version and exit.
--help Show this message and exit.
Commands:
command Command description goes here
There's just one remaining problem: the demo executable itself is nowhere to be found:
uv run demoTraceback (most recent call last):
File "<frozen runpy>", line 198, in _run_module_as_main
File "<frozen runpy>", line 88, in _run_code
File "/private/tmp/noc/demo/demo/__main__.py", line 1, in <module>
from .cli import cli
ImportError: attempted relative import with no known parent package
And if I peek in .venv:
ls .venv/binI see no demo and no pytest either:
activate
activate.bat
activate.csh
activate.fish
activate.nu
activate.ps1
activate_this.py
deactivate.bat
pydoc.bat
python
python3
python3.11
So unsetting CONDA_PREFIX fixes some but not all of my problems.
I still can't figure out what commands to run such that the executables defined in my pyproject.toml or for things I have installed like pytest end up in the .venv/bin folder and can be run using uv run name-of-executable.
From Charlie Marsh: