How to Use uv for Python Projects and Virtual Environments

How to Use uv for Python Projects and Virtual Environments

If you already use pip and venv, uv gives you a faster and more project-focused way to manage Python projects.

Instead of manually creating a virtual environment, activating it, installing packages, and trying to remember what belongs to the project, uv gives you one workflow for creating projects, adding dependencies, running code, and setting up the same project again later.

I recently wrote a blog post about Python virtual environments and shared it on LinkedIn.

The feedback was immediate.

One person told me they only use uv now. Someone else added that uv is a better alternative.

In the previous blog post, you learned why you should use virtual environments. TL;DR they give each Python project its own isolated space, so your packages stay organized and separate from other projects.

Now we’re talking about uv, which builds on top of that idea.

Managing Python dependencies and environments manually can be tedious. But with uv, you now have a modern way to create new Python projects, manage dependencies, work with virtual environments, and run your code. In other words, it helps with many of the things you would normally use pip and venv for, but in one tool.

So in this blog post, you’ll learn what uv is, how it compares to pip and venv, and how to use it with a simple requests example.

And in the end, you should be able to start a small Python project with uv and understand what files like pyproject.toml and uv.lock actually do.

What is uv?

uv helps you manage Python package and manage your projects. You can use it to create a project, create a venv, install packages, track your dependencies and run your code. The official documentation describes it as “an extremely fast Python package and project manager, written in Rust”. So uv combines multiple features into one single tool.

To create a virtual environment the old way, you could use this command:

python -m venv venv

And then you can use pip to install a package:

pip install requests

uv combines that into a simplified workflow. First, we have to initialize a project:

uv int

And then we have to add the requests library:

uv add requests

We still need two commands to achieve the same outcome, but the commands are simpler and easier to understand. It also doesn’t mean pip and venv are suddenly useless. They are still important to understand because they teach you what is happening in the background.

But with uv, you start to think more about the project, now the individual steps. This matters because your scripts often depend on external packages. Maybe today you are using requests to call an API. Tomorrow you might use netmiko, napalm, nornir, or pyyaml.

And that is where uv becomes so useful. It gives your project a clear structure. Your dependencies are defined in the project itself, uv creates and uses a virtual environment for that project, and it becomes much easier to set up the same project again later.

So instead of only thinking, “How can I install this package?”, you start thinking “How do I manage this project?”

How is uv different from pip and venv?

In my previous blog post about Python virtual environments, I explained why virtual environments matter and how they help keep your Python projects separated.

So I won’t repeat all of that here. So here’s a short table with the most common commands:

Tool or commandGoal
python -m venv venvCreates a new virtual environment
pipInstalls packages into an environment
uv addAdds a dependency to your project and records it in pyproject.toml
uv runRuns a command inside the project environment
uv syncSets up or updates the project environment from the project files
uv pipProvides pip-compatible commands for more manual or existing workflows

If you start using uv, you will usually use this project workflow:

  1. uv init
  2. uv add
  3. uv run
  4. uv sync

For example, with the classic venv and pip workflow, you might do something like this:

python -m venv venv
source venv/bin/activate
pip install requests
python main.py

That works fine, but uv simplifies that. We still have four commands, but they are simpler and do more:

uv init my-project
cd my-project
uv add requests
uv run python main.py

The difference is not just simpler commands. The bigger difference is that uv thinks in terms of the project, not individual dependencies.

When you add requests, uv does not only install it. It also adds it as a dependency of your project, updates the lockfile, and syncs the project environment. When you run your code with uv run, it uses the project environment for you.

So instead of manually creating an environment, activating it, installing packages, and trying to remember what belongs to the project, uv keeps the dependency information in the project files.

uv helps you work with Python projects in a more organized way, while still using the same basic idea you learned in the virtual environments blog post: each project should have its own environment with its own dependencies.

When should you use uv add vs uv pip install?

For a normal uv project, use uv add when you want to add a dependency to the project. For example:

uv add requests

This updates your project files and makes requests part of the project.

You may also see commands like uv pip install. Those are useful for more manual workflows or for people who want a faster, pip-compatible interface. But for a new uv project, uv add is usually the command you want.

Installing uv

Before you can use uv, you need to install it on your machine.

The official documentation shows a few different installation options, including the standalone installer and package managers like Homebrew, WinGet, and Scoop. For this blog post, we’ll keep it simple and use the standalone installer. See the official uv installation docs for more options.

On macOS or Linux, run:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows, run this in PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Once the installation is finished, check that uv is available:

uv --version

You should see something like this:

uv 0.11.14

If your terminal says that uv cannot be found, close your terminal and open a new one. Sometimes your shell needs to reload before it can find newly installed tools.

Once that works, you are ready to create your first uv project.

Creating your first uv project

Now that uv is installed, let’s create a small Python project. We’ll use a simple requests example, just like in the virtual environments post.

So first, let’s create a new project:

uv init uv-demo

Then move into the project folder:

cd uv-demo

When you run uv init, uv creates a basic Python project for you. By default, this includes files like main.py, README.md, .python-version, and pyproject.toml. Your project folder should now look something like this:

uv-demo
├── .git
├── .gitignore
├── .python-version
├── main.py
├── pyproject.toml
└── README.md

The main.py file is where you can write your Python code.

Open main.py, and you should see a small example like this:

def main():
    print("Hello from uv-demo!")

if __name__ == "__main__":
    main()

You can run this file with:

uv run python main.py

This is an important command! With the classic venv workflow, you normally activate the virtual environment first and then run your Python script.

With uv, you can use uv run. That tells uv to run this command inside the project environment.

The first time you run a project command like uv run, uv can create the .venv folder and uv.lock file for your project. The .venv folder is the virtual environment, and uv.lock stores the exact dependency versions for the project.

So after running your project, your folder may now look more like this:

uv-demo
├── .git
├── .gitignore
├── .python-version
├── .venv
├── main.py
├── pyproject.toml
├── README.md
└── uv.lock

At this point, you have a working Python project managed by uv. You have not installed any external packages yet, but the basic project structure is in place. Next, we’ll add requests and use it in main.py.

Adding your first package with uv

Right now, your project exists, but it does not use any external packages yet. Let’s change that by adding requests.

So inside your project folder, run the following command:

uv add requests

This does a few important things:

  1. uv adds requests to your project dependencies in the pyproject.toml file.
  2. uv updates the uv.lock file for your project.
  3. uv syncs the project environment, so the package is available when you run your code.

If you open pyproject.toml, you should see something like this:

[project]
name = "uv-demo"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "requests>=2.34.2",
]

Your version numbers may look slightly different, which is fine. The important part is this:

dependencies = [
    "requests>=2.34.2",
]

This tells you that requests is now a dependency of your project.

The requires-python value may also look different on your machine. For example, you may see >=3.11, >=3.12, or >=3.13, depending on the Python version selected for your project.

That is different from running pip install requests in an environment and trying to remember later what you installed. With uv, the dependency is written down as part of the project.

Now open main.py and replace the existing code with this:

import requests
  

def main():
    response = requests.get("https://httpbin.org/get", timeout=10)
    print(response.status_code)


if __name__ == "__main__":
    main()

This script sends a simple HTTP GET request to httpbin.org and prints the status code.

Now run your script:

$ uv run python main.py
200

If you see 200 in the output, it means your script was successful!

And notice what you did not have to do manually:

  • You didn’t create a virtual environment yourself
  • You didn’t have to activate the virtual environment yourself
  • You didn’t run pip install requests yourself

You told uv to add the package, and then you told uv to run the script inside the project environment.

For a small project, this may not look like a huge difference yet. But once your project starts using multiple packages, this workflow becomes more useful. Your dependencies are listed in one place, your environment belongs to the project, and uv run makes sure your code runs with the packages your project needs.

What are pyproject.toml and uv.lock?

After running uv add requests, two files become especially important: pyproject.toml and uv.lock.

These two files are the reason uv feels different from just running pip install.

At first, these files can look a bit complicated. But the idea behind them is quite simple:

  • pyproject.toml describes your project
  • uv.lock records the exact package versions your project uses

The pyproject.toml file is the main project file. It contains information about your project, including its dependencies. For example, after running uv add requests, the requests is added to pyproject.toml. So this file answers the question “What does this project need to run?”

The uv.lock file is more specific. It records the exact package versions that were installed.

But why do we need that? Doesn’t pyproject.toml already show that requests was installed?

Well yes, but uv.lock goes further. It does not only record the version of requests. It also records the packages that requests depends on. When you install one Python package, it often needs other packages in the background. uv.lock keeps track of those exact versions too.

This matters when you want to run the same project again later. Maybe you share the project with another engineer. Maybe you push it to GitHub. Maybe you come back to it in six months.

The uv.lock file helps uv recreate the same environment with the same package versions.

And this is where another command becomes useful:

uv sync

uv sync makes sure your project environment matches the dependencies defined in your project files. Simply put, it looks at your project and installs what is needed into the project environment. The official docs describe syncing as installing packages from the lockfile into the project environment. They also explain that commands like uv run automatically lock and sync before running your code, so you do not always need to run uv sync manually.

So on a daily basis, you’ll run the following commands quite often.

uv add requests
uv run python main.py

And if you open an existing project later, especially one you cloned from GitHub, you may this command to sync your environment

uv sync

That tells uv to “set up this project environment using the dependencies from the project files.”

Go ahead and look at your uv.lock file. It is probably quite large. But don’t worry, you usually do not edit it yourself, uv manages it for you!

Which files should you commit?

If you use Git, you may now wonder which files should be committed.

For most projects, you should commit these files:

  • pyproject.toml
  • uv.lock
  • .python-version
  • your source code, for example main.py

You should not commit .venv. The .venv folder is your local virtual environment. It’s quite large and it can be recreated with uv sync, so it should stay out of Git.

Basic uv commands you should know

I briefly talked about the different commands. You do not need to know every uv command to get started. But for now, I think these are the most useful commands.

Create New Project

uv init uv-demo

Creates a new Python project called uv-demo.

Add New Dependency

uv add requests

Adds the requests package to your project. This updates pyproject.toml, updates the lockfile, and syncs the project environment.

Remove Dependency

uv remove requests

Removes a package from your project. So if you added a package by mistake, you can remove it again.

Run a Project

uv run python main.py

Runs your Python file inside the project environment. This is one of the commands you will probably use the most.

Sync the Project Environment

uv sync

Sets up the project environment based on your project files. This is useful when you open an existing project, for example after cloning it from GitHub.

I think that is enough to get started. There is much more uv can do, but you do not need all of it on day one. If you understand uv init, uv add, uv run, and uv sync, you already understand the core workflow for small Python projects.

Final thoughts

If you already understand pip and venv, uv is not a completely new concept. It is a better workflow around the same core idea: every project should have its own environment and clearly defined dependencies.

For small automation scripts, you can start with this workflow:

uv init
uv add requests
uv run python main.py

Right now I think that’s enough to get started.

Later, you can explore more advanced features like Python version management, dependency groups, tools, and exporting requirements files.

uv is also important if you’re preparing for the CCIE Automation certification. uv is now also part of the latest candidate workstation (CWS). But after reading this blog, you should now be familiar with that. And if you want to learn more, it would be nice having you as student of my CCIE Automation e-learning :)

0 0 votes
Content Rating
Subscribe
Notify of
0 Comments
Oldest
Newest Most Voted