Mastering Python Project Management with uv Part 1: It’s Time to Ditch Poetry!

Thomas Bury
5 min readSep 29, 2024

--

🛠 Updated on 23rd Feb 2025
This article has been revised based on valuable feedback from readers, thank you all!

Are you tired of juggling multiple tools like pip, virtualenv, conda, poetry, and pyenv just to keep your Python environments and dependencies in check? You’re not alone! Managing Python projects can feel like a headache, especially with all the different package managers and tools you have to wrangle.

Enter uv— the Universal Virtualenv. Think of it as a one-stop-shop package manager designed to streamline and speed up your Python development process.

— -

A Little Backstory

uv draws its inspiration from Rye, another modern packaging manager, to unify the best features of pip, pip-tools, pyenv, virtualenv, and poetry. Built using Rust, uv is not just fast but highly efficient, simplifying everything from managing dependencies to creating virtual environments.

The Aim of uv
In a nutshell, uv is about consolidation. Why switch between multiple tools when you can have one unified experience? It aims to remove the friction from Python development, offering you a more consistent and faster way to manage your projects. And it’s also blazing fast! That opens new doors, for dynamic management

1. Portable Code with Inline Script Metadata

Let’s Talk Dependencies
One of the most exciting features of uv is the ability to add dependencies directly within your Python script. Imagine you have a simple script like this:

# app.py
import requests
from rich.pretty import pprint
response = requests.get("https://peps.python.org/api/peps.json")
data = response.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])

Running this script usually means setting up a virtual environment and installing dependencies manually. With uv, you can embed all your dependencies directly into the script, making it self-contained and shareable:

uv add --script app.py 'requests<3' 'rich'

Automatic Metadata Generation
This adds metadata to the script file:

# app.py

# /// script
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///

import requests
from rich.pretty import pprint

response = requests.get("https://peps.python.org/api/peps.json")
data = response.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])

And that’s it! You can share this file with someone else, and they can simply run:

uv run app.py

And voilà — no external setup required! UV will parse the metadata and create an ephemeral virtual environment for you. All thanks to uv’s speed and efficiency.

  • Declare the script dependencies, using the command uv add --script app.py "requests<3" "rich"
  • Run the script using uv run app.py
  • Let uv do the rest

2. Creating and Managing Virtual Environments

Getting Started with Virtual Environments
By default, `uv` requires packages to be installed within virtual environments to keep your system clean and avoid conflicts between different projects. Creating a virtual environment with `uv` is simple:

uv venv

This will create a `.venv` directory containing the isolated environment. If you want to specify a custom directory or Python version, you can do:

uv venv my_env --python 3.9

The environment is ready to use, and `uv` will detect it automatically for all your commands, like installing packages or running scripts.

When to Use uv add vs. uv pip install

  • Use uv add: When you want to add dependencies to your project’s pyproject.toml file. This is best when you are developing a project and want to keep track of all dependencies, making the project easily shareable and reproducible. We will cover this in the next post, stay tuned!
uv add fastapi

This will update your pyproject.toml and lock the version in uv.lock.

  • Use `uv pip install`: When you want to install packages for quick use without modifying the project file, or for global tools where you don’t need to track them in a pyproject.toml. Think of uv pip as an immediate, one-off installation. However, be aware it is intended to be used in legacy workflows or cases where the high-level commands do not provide enough control and the environment is then manually managed
uv pip install requests

Choosing the right command ensures your project is properly managed and easy to share or deploy.

Comparison of installing dependencies using “uv add” or “uv pip install” — Image by Author

— -

3. Lock Versions for Reproducibility

Ever Had Your Code Break Due to Updates?
We’ve all been there — your code works today, then breaks tomorrow because a package gets updated. With uv, you can prevent this by locking package versions to ensure consistency and reproducibility.

After adding your dependencies, generate a lockfile:

uv lock

This creates a uv.lock file that records the exact versions of your dependencies. Committing this lockfile to your version control system ensures that everyone working on the project uses the same dependency versions.

You can even fine-tune versions based on higher-level rules, in the TOML:

[tool.uv]
exclude-newer = "2023–10–16T00:00:00Z"

This way, even if new versions of your dependencies come out, your project remains stable. Perfect for long-term projects where you can’t afford surprises!

— -

4. Managing Python Versions

Different Projects, Different Python Versions? No Problem!
Many developers have to work on multiple projects that require different Python versions. `uv` makes switching versions as easy as:

uv python install 3.8 3.9 3.10

Once the versions are installed, switching between them is seamless:

uv run - python 3.10 app.py

And if you want to lock a specific version for a project:

uv python pin 3.9

No more juggling pyenv commands — uv handles all the heavy lifting for you.

5. Manage CLI Tools Globally and Easily

From black to ruff, Get Your Tools Hassle-Free
Whether you’re linting code or formatting files, uv makes installing CLI tools easy:

  • Globally:
uv tool install ruff
  • Locally within a Project:
uv add ruff
  • Run Ephemeral Commands without Installing Globally:
uvx black my_code.py

Say goodbye to package conflicts and environment pollution — just run your tools whenever and wherever you need them.

Three ways of using development dependencies in UV — image by author

— -

If you’re looking to supercharge your Python development and want to stop wrestling with multiple tools, uv is your answer. With its streamlined commands, reproducible environments, and efficient package management, uv makes Python development a pleasure rather than a chore.

Ready to take `uv` for a spin? 🚀 Start today and experience a better way to manage your Python projects.

— -

Stay tuned for Part 2, where we’ll dive deeper into advanced features like leveraging pyproject.toml, handling global vs. local tool installations, and how `uv` can be your best friend when managing complex environments.

Happy coding! 🐍✨

For more details and full documentation, check out uv documentation, the part 2 and part 3 of the guide

--

--

Thomas Bury
Thomas Bury

Written by Thomas Bury

Physicist by passion and training, Data Scientist and MLE for a living (it's fun too), interdisciplinary by conviction.

Responses (11)