Astral.sh recently released a new tool called uv and it replaces pip, pyenv, poetry and more!
This is something I saw on Hacker News a little while back and meant to try but never really had the opportunity. Now that I’m planning on writing another blog post which requires starting a new Python project I figured this was the perfect opportunity.
They give you a few installation options, I chose the classic curl
option.
curl -LsSf https://astral.sh/uv/install.sh | sh
It installs in a few seconds then asks you to restart your shell. I do that and I’m ready to go.
They claim in their documentation that uv
is 80 times faster than python -m venv
at creating a virtual environment, so that’s something I’d like to test. Not because I need my venv creation to be quick but I just want to see how accurate their marketing material is to real life.
uv
$ time uv venv venv_a
Using Python 3.10.7 interpreter at: /Users/john/.pyenv/versions/3.10.7/bin/python3
Creating virtualenv at: venv_a
Activate with: source venv_a/bin/activate
real 0m0.250s
user 0m0.043s
sys 0m0.038s
venv
$ time python -m venv venv_b
real 0m2.012s
user 0m1.537s
sys 0m0.251s
Interesting. I ran these tests a dozen times and the results weren’t any different.
Since we really only care about the real time, it looks like uv
creates a virtual environment
in 0.25s and venv
does it in 2s.
While this is faster, roughly 8x, its not the 80x they reported, but I’m going to say that since their marketing material was only off by an order of magnitude, it counts as “accurate” (lol).
The extra output is nice though, I really like the line showing me which interpreter I’m using: Using Python 3.10.7 interpreter at: /Users/john/.pyenv/versions/3.10.7/bin/python3
And the line Activate with: source venv_a/bin/activate
will be helpful for beginners.
There is another cool feature of uv, where if you create a venv like so: uv venv
and it doesn’t exist, it creates it, but if you run that same command again it will detect the venv exists and automatically source it for you which is neat.
Now lets test some package installs. Since I’m starting work on another blog post I’ll just use that for my tests.
I created a simple requirements.txt file with the following:
ollama
fastapi
Then ran (venv) $ time pip install -r requirements.txt
This took 2.7s of wall clock time and grabbed [email protected]
and [email protected]
Lets see what happens when we do the same but using uv as a drop-in replacement.
I call uv venv
to create the virtual environment and then uv venv
again to source it and
this is where I ran into the first problem.
The docs say “When using the default virtual environment name, uv will automatically find and use the virtual environment during subsequent invocations.” but when I do that it isn’t automatically sourcing the bin/activate file. Weird.
After manually sourcing the file I ran the same install command but with uv instead of pip this time:
(venv) $ time uv pip install -r requirements.txt
This took 0.6s and gave me a pretty cool summary afterwards of all the packages it installed and what versions they were.
It also resolved the ollama
and fastapi
packages to the same version as pip which is mainly what I was testing.
The next feature I want to try is project initialization and management
$ uv init --name fastapi-ollama --lib
Initialized project `fastapi-ollama`
It creates a nice pyproject.toml
for us that looks like this:
[project]
name = "fastapi-ollama"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
Let me just say here, I’m extremely happy to see that uv is
generating PEP-621 compliant pyproject
files, unlike Poetry. The fact that it’s creating and using project.dependencies
instead of
Poetry’s tool.poetry.dependencies
is a major reason for me to make the switch.
It also created a new file src/fastapi-ollama/__init__.py
which contains a simple hello()
function.
Interestingly it defaults to using hatch as the build backend. It also created a src directory, which means they prefer a src-layout as opposed to the flat-layout which I’m used to seeing.
Now lets build and run it. According to the docs we add an entrypoint to the toml file:
[project.scripts]
hello = "fastapi_ollama:hello"
NOTE: The actual package and directory name is fastapi-ollama (with a hyphen) but python modules require you use an underscore, hence the line above being
fastapi_ollama
and notfastapi-ollama
.
Then we build and run with uv run hello
and we get:
Built fastapi-ollama @ file:///Users/john/prog/python/fastapi-ollama
Uninstalled 1 package in 0.90ms
Installed 1 package in 1ms
Hello world!
Now lets add a dependency and run it
uv add requests
That added the following to the pyproject.toml:
dependencies = [
"requests>=2.32.3",
]
Ok lets update the hello() function to use requests, i’ll just grab the weather, and then we can test it again.
import requests
def hello() -> str:
url = 'https://wttr.in?format=3'
return requests.get(url).text
$ uv run hello
Raleigh, North Carolina, United States: ⛅️ +66°F
Cool, works very similarly to Poetry which is what i’ve been using the past few years.
Another major feature of interest to me is the ability for uv
to manage your installed python versions.
$ uv python list
cpython-3.12.3-macos-aarch64-none /opt/homebrew/opt/python@3.12/bin/python3.12 -> ../Frameworks/Python.framework/Versions/3.12/bin/python3.12
cpython-3.11.7-macos-aarch64-none /opt/homebrew/opt/python@3.11/bin/python3.11 -> ../Frameworks/Python.framework/Versions/3.11/bin/python3.11
cpython-3.10.7-macos-aarch64-none /Users/john/.pyenv/versions/3.10.7/bin/python3.10
cpython-3.10.7-macos-aarch64-none /Users/john/.pyenv/versions/3.10.7/bin/python3 -> python3.10
cpython-3.10.7-macos-aarch64-none /Users/john/.pyenv/versions/3.10.7/bin/python -> python3.10
pypy-3.10.14-macos-aarch64-none <download available>
pypy-3.9.19-macos-aarch64-none <download available>
pypy-3.8.16-macos-aarch64-none <download available>
As you can see I usually use pyenv
to manage my interpreters, so the ability for uv to do it as well would be great.
I really like that fact that it shows me all my Python installs and not just the ones that were installed via uv.
A quick test to install a version I don’t have:
$ uv python install 3.12.4
Searching for Python versions matching: Python 3.12.4
Installed Python 3.12.4 in 1.02s
+ cpython-3.12.4-macos-aarch64-none
$ uv python list
...
cpython-3.12.4-macos-aarch64-none /Users/john/.local/share/uv/python/cpython-3.12.4-macos-aarch64-none/bin/python3
Well thats cool, when I first heard about uv
I didn’t think it’d be this easy to use OR that it could replace both Poetry
and pyenv
.
The last feature I want to test is installing Python projects with uv
inside Docker containers.
If it’s easier than Poetry then I’ll proceed to start using uv in all my new projects and eventually migrate old ones if I decide to stick with it.
According to their docs there are 3 ways to use uv
in a Dockerfile:
-
Use one of their pre-built images
FROM uv:python3.12-alpine
-
Copy the binary from their distroless image into yours
COPY --from=ghcr.io/astral-sh/uv:0.4.8 /uv /bin/uv
-
Install it via the regular installer (not recommended)
We’re going to skip this step since it isn’t recommended
To keep things simple i’ll just use one of their pre-built images for now. Here’s what my test Dockerfile looks like:
FROM ghcr.io/astral-sh/uv:0.4.9-python3.10-alpine
WORKDIR /app
COPY . /app
RUN uv sync --frozen
CMD ["uv", "run", "hello"]
Now we build and run the image
$ docker build -t uv-test:dev .
$ docker run uv-test:dev
Raleigh, North Carolina, United States: ⛅️ +65°F
Exactly as expected!
My first impression of uv
is very good. It replaces many tools I already use, its fast,
and even though it’s new (their first release was earlier this year) and isn’t 1.0 yet, they release
often and the developers seem to engage with the community well.
As you probably noticed from the package name I chose in my examples I’m going working on integrating FastAPI with Ollama. For the past few months Ollama is something I’ve spent a lot of free time playing around with and I’ve been enjoying it a lot and wanted to write some basic blog posts about it.