UV — Intuitively and Exhaustively Explained
A new standard for Python project and package management

UV is the new and hot project/environment/dependency manager taking the Python world by storm. The GitHub stars are off the chats:
But, more importantly, it’s being used all over the shop in high-profile, cutting-edge repos that are governing the way modern software is evolving. Anthropic’s Python repo for MCP uses UV, Google’s Python repo for A2A uses UV, Open-WebUI seems to use UV, and that’s just to name a few.
It came out of nowhere, and it’s changing the way people make projects in Python. Let’s talk about it.
Who is this useful for? Anyone developing in Python.
How advanced is this post? This article is accessible to developers of all levels.
Prerequisites: None, but if you’re not a developer or plan on being one, I have no idea why you would want to read any of this. go read something more interesting instead:
Dependencies, Environments, and Projects
For those uncut by the need, I envy you. For those who have been working in Python for a while, you know exactly what I’m about to talk about.
If you’re a fledgling developer who’s starting to learn Python, you might do a bit of googling and be directed to install Python onto your computer. Python comes with a package manager called pip
(which, cutely, stands for “pip installs packages”). If you’re on Windows you’ll likely need to mess around with environment variables and maybe restart your computer. If you’re on mac you might realize “oh hey, I already have Python installed”. Either way, you get to the point where you can install new packages with commands like pip install <package>
and you can run python files with python <python_file>.py
(or maybe python3 <python_file>.py
).
So, you play around, and have a merry old time. You build a few projects with some different dependencies; some require pandas
, some require uvicorn
, some require numpy
, scipy
, pytorch
, etc. You install a whole bunch of libraries, learn them, and take the world by storm.
Then, you go back to one of your older projects to use as a reference, and it doesn’t work. You get an import error on some esoteric library you’ve never heard of, which is a dependency of a dependency of a dependency of one of the libraries used in the project you’re trying to run. After a while, you realized that the project you just made uses a version of a dependency that isn’t compatible with this project. If you’re on Mac, you might realize that messing with the pre-installed version of Python wasn’t such a good idea after all.

You do a bit more Googling (or, maybe ChatGPTing for my younger readers. I’m 27, this is the first time I’ve ever earnestly felt old), and learn that a lot of developers use something called virtualenv
or conda
to do something called “environment management”. Basically, the idea is that you can create separate environments of python, each with their own dependencies and maybe even Python versions. This allows you to have a container of python dependencies for one project, and another container for another project.
This gets you pretty far. You build the habit of creating and curating different environments, and you activate different ones for different projects. That might be good enough, but for many developers, this is cumbersome. If you accidentally enable the wrong environment for a project, you can damage the dependencies, thus damaging a bunch of other projects that rely on that environment.
After a bit more Googling/GPTing you might stumble upon poetry
, which I honestly haven’t used very much. I know, I know, how dare I call myself a Python developer. I’m a data scientist, so I often find myself in completely isolated and ephemeral environments running on the cloud anyway. Because I haven’t used poetry
a ton, I can’t speak for its cost and benefits; I can only give you the high-level touchpoints.
Basically, poetry
is a set of tools used to make dependency and package management easier, allowing you to do things like environment management, dependency installation, versioning, and other stuff all with a single tool. From what I’ve read, it’s great if you’re working in a team of people and standardizing building and deploying is a must. I’ve also read that it can be a bit heavy, and enforces rules that you need to follow to use it effectively. I recommend this video, which does a nice job of quickly going through some of the costs and benefits of UV vs poetry. From what I gather, poetry is great for application developers working collaboratively on Python projects, but if you’re whipping up personal projects or sharing lightweight repos it can be a bit of a headache.
Anywho, you’re now a bigshot Python developer who’s working on cool projects. Maybe you start working with MCP, A2A, or some other new fancy tech, and you bump into this new thing called “UV”. What is uv
? How does it work? What’s it good for?
Let’s get into it.
The core Commands of UV
For some of you, all I need to tell you is that uv
is written in Rust. For others, all I need to tell you is that uv
is like the npm
of Python. For the rest, here’s how to install uv
, and what you can do with it.
Installing uv
uv
install instructions are defined here. You can install uv
either with curl
curl -LsSf https://astral.sh/uv/install.sh | sh
or even with pip
pipx install uv
Check out the uv docs for more information on installation. It seems like using curl -LsSf https://astral.sh/uv/install.sh | sh
is recommended in Linux and Windows, and powershell -ExecutionPolicy ByPass -c “irm https://astral.sh/uv/install.ps1 | iex”
is recommended on Windows (or just use the linux subsystem in Windows like a normal person).
uv init
uv
does project, package, and environment management in python. When you run uv init
in a particular folder, uv
builds the necessary resources to create an isolated Python project. Upon running uv init
, five files are created:
a
.gitignore
, which prompts git to ignore certain generated and environment files. This is standard practice, souv
doing it is a nice convenience.a
.pythonversion
file, which is used for managing Python environments.uv
can be used to specify different Python versions for different environments, which we’ll discuss later.a
main.py
, which is a simple sample “hello world” style script.a
project.toml
file, which details the name of the project, information about the Python version, a description of the project, the location of the top-level readme file for the project, and dependencies.a
README.md
file, which is an empty README file for defining top-level documentation for the project.a
uv.lock
file, which records granular information about dependencies and sub-dependencies. This allowsuv
to granularly record and re-build dependencies.
Thus, the uv init
command can be run in a folder to turn that folder into a uv
managed project.
uv add
Once you have a uv
initialized project, you can install new dependencies to that project with the uv add
command. uv
can install from pypi, just like pip
, conda
, and poetry
, so you can install all your favorite open-source dependencies just like you would with pip
. Here’s some examples, the last one showing that multiple dependencies can be installed in a single line.
uv add pandas
uv add scipy
uv add numpy sklearn matplotlib
When you install dependencies, these are added to the pyproject.toml
and granular information is added to the uv.lock
file. This allows uv
to track the dependencies in a project.
You can also install dependencies from a valid GitHub repo
uv add git+https://github.com/encode/httpx
or other sources. These are the following dependency sources that are supported by uv (source):
Index: A package resolved from a specific package index.
Git: A Git repository.
URL: A remote wheel or source distribution.
Path: A local wheel, source distribution, or project directory.
Workspace: A member of the current workspace.
Realistically, most users will just be installing from PyPi the vast majority (if not all) of the time.
uv remove
if you want to remove a dependency from the project, the uv remove
command can be used. This will uninstall the dependency, and update pyproject.toml
and uv.lock
to respect the change. For instance, if we wanted to uninstall matplotlib from a particular project, we would run
uv remove matplotlib
uv sync
After you’ve added dependencies to a project, then maybe committed that project to a repo, when other people clone that repo they’ll need to install those dependencies. uv sync
synchronizes the dependencies with the uv.lock
file. If you clone a repo that’s been managed with uv
, this will download all the necessary dependencies.
uv
does a great job automatically synchronizing projects, and often calls uv sync
automatically under the hood before executing other key commands like uv run
, so you may not need to use this. It’s useful to know, though.
uv run
If we want to run a Python script within our environment, we can use the uv run
command. This will ensure that the script is run correctly with the correct dependencies and Python version.
uv run main.py
You don’t have to exclusively run Python files directly either. If you’re using something like mcp
inside your project, and you want to spool up the dev
server to experiment with an mcp
server’s functionality, you can run something like
uv mcp dev server.py
This runs mcp
in dev
mode based on the server.py
file within your current uv
environment.
There’s a ton of other cool stuff you can do with uv run
(docs), but the core workflow is pretty simple.
Managing Python Versions
When you create a uv
initialized project, the pyproject.toml
has a requires-python
field which, as far as I can tell, is always specified as >=3.11
. You can modify this, but it seems like this is the recommended minimum for using uv
, which is reasonable considering Python 3.11 was released in 2022.
By default, uv
will work with whatever Python version you have lying around. However, you can specify a specific Python version with uv python pin <version>
. So, if you want to create a new uv
managed Python project that uses Python 3.12, you can do that with the following command:
uv init
uv python pin 3.12
This will update the .python-version
file.
If we run the sample main.py
file with uv run main.py
, you’ll notice a .venv
folder will be created. This will define a virtual environment containing the necessary resources for running Python 3.12.
Using uv
Ok great, I think we’ve covered most of the basics. Let’s explore how uv
can practically be used.
Example 1) Defining a Series of Projects
When developing a complex project, a common approach I take is to make a bunch of little sub-projects before diving into the full thing. This helps me understand some of the subtleties of my plan, and refine my plan with more knowledge before taking the big bite out of the larger project.
UV is great for this, because you can simply create new folders and run uv init
in them to define a completely new version. If you want to copy dependencies from one project to another, you can just copy the dependencies
in the project.toml
file from an old project to a new one and run uv sync
.
Then, as you bounce around between different projects, you can use uv run
to run scripts in different projects while using the dependencies for that particular project. I did this exact strategy when working on understanding A2A for my recent article on the subject, and it was super convenient.
Example 1) Cloning a Pre-Existing Project
If you clone a uv
managed project from github, uv does a bunch of cool stuff automagically. If we cloned my A2A demo repo, for instance
git clone https://github.com/DanielWarfield1/A2A-Demos.git
and if we then entered into one of the demos
cd demo10
we can then see scripts like car_agnet.py
, airline_agent.py
, etc. Which are all runnable. if we call
uv run airline_agent.py
uv
will automatically detect that a virtual environment doesn’t exist yet, and that the proper dependencies haven’t been installed. It’ll set all that up real quick before running our script.
And when I say “real quick”, I mean it. uv
is blazingly fast at setting up things like virtual environments and packages, partially because it’s written in rust. When exploring uv
vs other package and environment managers, you’re likely to see the following graph passed around
This speed makes these on-the-fly installations feel effortless. uv
is really a remarkable example of “I love it because I don’t have to think about it”.
Conclusion
I’ll be using uv
for managing all of my personal and writing Python projects moving forward. If I’m not using some managed Jupyter environment like Google Colab, I’ll be using uv
. It’s intuitive, fast, and effortlessly easy to use. I’m also (surprisingly to myself) excited to experiment with poetry
at some point in the future. I’ve always found poetry
to feel excessively heavy, but now that I have a tool for quick, light work, I can keep poetry
in the back of my mind if uv
feels like it’s not quite robust enough for a big new project.
I’m a big fan of Jupyter, the cell execution and interwoven documentation are a godsend for deep theoretical work, which is most of what I do. I’ll be honest, though, a big reason I use Colab is because project management has been a cumbersome issue in Python for as long as I can remember, and I would rather spool up an ephemeral compute instance on the cloud than deal with it. I think, with uv
, I’ll find myself doing a lot more local Python development moving forward.
This dropped right on time as I am starting up a python based project for the first time in a while. Getting the right version and packages is always a pain!
Been using uv for sometime and I really love the simplicity. It is fairly easy to learn too.