Podcast Title

Author Name

0:00
0:00
Album Art

UV Explained: A Faster, Simpler Python Workflow in 5 Minutes

By 10xdev team August 09, 2025

Welcome to this article where we're going to take a look at UV, an all-in-one tool that will make working with your Python project simpler and faster. UV is a tool that handles your virtual environment, package management, dependency resolution, and Python version all in one place. It is also very fast. This basically allows it to replace a ton of other tools that you might already be using in your Python workflow.

For example, many developers have been using pip and venv for over a decade. But the increased speed and simplicity offered by UV is just too good to ignore, which is why it's gained such rapid adoption. And now even more so in the age of AI agents and MCP servers, where it helps to have a simple way to manage and execute Python packages.

So today, let's take a quick look at UV and how we can use it. - First, we'll explore how to set up a project with UV and how it manages your virtual environment automatically. - Then, we'll look at package management and how UV replaces pip with something simpler and faster. - Finally, we'll take a look at uvx, a one-line command that lets you run Python tools directly without having to manually set up or install them.

Let's get started.

Getting Started: Installing UV

First, let's install UV. If you're on macOS, you can use the Homebrew package manager. If you're on Windows, you can use winget.

On macOS: bash brew install uv

On Windows: bash winget install uv

For other platforms or different installation methods, check the official documentation for more instructions. Once it has been installed, you can verify that it's working by running this command to check the version:

uv --version

Creating Your First Project

We can build a simple API server to see how UV handles everything. You can create a project by using the init command, followed by the name of the project. This will create the directory for you.

uv init my-fastapi-project

If you already have an existing project or an existing directory, you can also just run uv init without having to specify the project name.

Behind the scenes, this init command will set up a couple of important files for you inside the project directory:

  • pyproject.toml: This is where your project configuration lives. It's similar to package.json or the requirements.txt file that you might be used to. It will have all your project metadata and all of your dependencies in one place.
  • README.md: A standard README file.
  • main.py: An entry point for your project.

Once this is all set up, you can add a dependency by just running the uv add command. For example, this line will add the FastAPI dependency to our project configuration and install it into our virtual environment.

uv add fastapi

But wait, you might have noticed that we didn't explicitly create or activate a virtual environment yet. That's exactly right. UV will create and manage this for us behind the scenes as long as we're in this project directory.

A Practical Walkthrough

Let's see this in action. First, we'll run uv --version to confirm that it's installed. Then, we'll run the uv init command to create a project.

uv init my-fastapi-project

This creates the my-fastapi-project directory. Let's move into that project and list the files inside:

cd my-fastapi-project
ls

You'll see the main.py, pyproject.toml, and README.md files. The initial main.py file is simple:

# main.py
def main() -> int:
    print("Hello, world!")
    return 0

We can try running it by typing uv run. The first time you run something, UV automatically creates a virtual environment for you and picks the Python version it wants to use.

uv run

Now let's add a dependency to the project:

uv add fastapi

After adding FastAPI, if you look in the project directory, you'll see that a uv.lock file has been created. This is a very long file that contains the exact version of every dependency used in this project, ensuring deterministic builds.

Now, let's open up our project in an editor like VSCode and replace the main.py script with a simple FastAPI server:

# main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

You'll notice that the dependencies all work fine. That's because modern editors like VSCode can detect the virtual environment created for us by UV.

Note: If you have any problems where you've already installed a dependency but your editor complains, just make sure that you check the Python interpreter and that it is set to the virtual environment for this project.

If you look into the pyproject.toml file, you'll also see that the FastAPI package has been added to the dependencies list.

To run the script, let's go back to the terminal. Instead of using the python command, we will use uv run. You can run whatever you'd normally use Python to run, except that this will use the correct virtual environment and pull in any packages it needs.

For example, to run a FastAPI application, we'd typically use uvicorn. We didn't explicitly install uvicorn in this environment, but let's see what happens when we run it:

uv run uvicorn main:app --reload

No problem at all. UV figures out that it needs uvicorn, sets it up in an isolated environment, and uses it to run our app. If you go to the provided URL (e.g., http://127.0.0.1:8000), you will see the FastAPI server running.

As you can see, this workflow is much simpler than the traditional combination of venv and pip. For comparison, here's a snippet of the commands previously required:

# Old workflow
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

This old method uses two different tools and is not very intuitive. You have to create the virtual environment manually and remember to activate it every time you want to work on the project. UV significantly simplifies this part of the workflow.

Advanced Package Management with UV

Now that we've got the basics down, let's look at how UV handles package management differently from pip. The main difference is that UV uses a proper dependency resolver and creates a deterministic lock file.

Let's add a couple of new dependencies to our project:

uv add "redis" "sqlalchemy"

UV uses the pyproject.toml file instead of the requirements.txt file. When we add new packages, it automatically updates this file for us, which is something pip doesn't do for requirements.txt. After running that command, you'll see that redis and sqlalchemy have been added to your configuration.

As it's doing this, UV also creates and updates the uv.lock file. This contains the exact version of every package used in the environment, so that whenever you build this project, you get the exact same results. Without this version locking, new package versions can introduce breaking changes. Version locking prevents this and is a best practice for any production-grade software.

To check which packages have been installed, you can run the uv tree command. This will show you the dependency tree of your entire project, along with the installed versions.

uv tree

For development dependencies, UV keeps them separate. Just use the --dev flag with the add command. Here, we'll add the PyTest framework for development purposes only, not for the production build.

uv add pytest --dev

If we go back to the configuration file, you'll see that pytest has been added under a [project.optional-dependencies] group for development.

Keeping Your Environment in Sync

If someone else clones your project, they can run the uv sync command to set everything up. This will read the lock file and recreate the exact same environment, ensuring consistent builds.

You can also use this command whenever you update the dependencies in your pyproject.toml file, whether to add or remove a package. It will figure out what to do.

Let's try it. Go to your pyproject.toml file and just remove sqlalchemy from the dependencies. After saving the file, go back to the terminal and run:

uv sync

You'll see that it has uninstalled SQLAlchemy because it realizes the environment is now different from the configuration. If you run it again, nothing will happen because the environment and configuration are now in sync.

Alternatively, instead of editing the configuration file directly, you can remove a package through the command line:

uv remove redis

This will remove the package from your dependencies, your lock file, and your environment. Compared to pip, this is a much cleaner approach to managing your packages.

Running Tools On-the-Fly with UVX

The last thing we're going to cover is uvx, which is UV's solution for running Python tools without installing them globally or adding them to your project.

To demonstrate this, imagine you've intentionally messed up the formatting of your main.py file. Let's say you want to format this code with black, but you don't want to add it to your project dependencies. Without UV, you'd have to either install it globally, risking version conflicts, or use another tool like pipx.

Once again, UV simplifies this. You can just run a tool using the uvx command, followed by the name of the tool and any of its arguments.

uvx black main.py

Behind the scenes, UV automatically creates a temporary, isolated environment, installs the tool there, and then executes it. This is also cached, so it's much faster the second time around. After running the command, you'll see that the formatting in your editor is now fixed, and this action didn't affect your project dependencies at all.

Here are a few other examples of uvx commands to give you an idea of how it works:

# Run a specific version of a tool
uvx --package cowsay==5.0 cowsay "Hello from uvx"

# Start a simple HTTP server
uvx http.server

# Run a Jupyter notebook
uvx jupyter lab

You can generally run any tool that is available on PyPI. If you have tools that you use frequently, you can also just install them globally but in an isolated way using uv tool install.

Why This Matters for Modern Development

Recently, building AI agents that use MCP servers has become more common, and uvx is especially relevant here because many MCP servers are developed in Python. If you're working with a CLI agent and want to use an MCP tool someone else has built, you can just use the uvx command to spin up that tool without ever having to install it or set up an environment for it yourself. This one use case alone can be a powerful reason to start looking into UV.

A Note on Speed

Whenever UV is mentioned online, its speed is brought up as a significant advantage over traditional tools like pip. This is partly because it is implemented in Rust, a high-performance language, and also because of its aggressive caching strategy.

However, the speed of a package manager has never really been a bottleneck for many developers. The focus of this article isn't on speed, because simplicity and reliability are often more important factors for choosing a tool. That said, one case where the speed of package and environment setup could be really useful is in a CI/CD pipeline, where the Python build step might be slow enough to be a bottleneck.

Try UV on Your Existing Projects

If you like the idea of UV, you don't have to wait until you start a new project. You can use it with your existing projects right now. Just install UV and then run uv init in your project directory. Your existing pip-based project will work fine, as UV can read the requirements.txt file and help you migrate. Give it a go and see how it can streamline your workflow.

Join the 10xdev Community

Subscribe and get 8+ free PDFs that contain detailed roadmaps with recommended learning periods for each programming language or field, along with links to free resources such as books, YouTube tutorials, and courses with certificates.

Recommended For You

Up Next