Skip to content

UV package manager & Ruff Formatter - User Guide

This guide helps you modernize your Python tooling using uv and ruff.

Table of Contents

  • introduction to uv
  • migrating from raw pip & requirements.txt to uv & pyproject.toml.
  • managing multiple python versions (old codebases)
  • introduction to ruff
  • ruff configuration in pyproject.toml
  • useful ruff commands

UV: Fast Python Package Manager

uv is a blazing-fast Python package manager and virtual environment manager developed by Astral. It replaces tools like pip, virtualenv, pip-tools, and even pyenv, providing:

  • Fast dependency resolution
  • Built-in virtualenv management
  • Support for multiple Python versions
  • npx-like tool execution via uv run

Installation

Install uv using either curl or pip:

curl -LsSf https://astral.sh/uv/install.sh | sh  # recommended
# or
pipx install uv
# or
pip install uv

To update uv, run uv self upgrade or pipx upgrade uv depending on how you installed it:

uv self upgrade  # recommended
# or
pipx upgrade uv
# or
pip install --upgrade uv

Quick Start

You can create a new project using uv init:

uv init myproject
The command creates the following directory structure under the myproject/ folder:
myproject/
├── .git/
├── .gitignore
├── .python-version
├── README.md
├── main.py
└── pyproject.toml

You can explore that it generates a README file, pyproject.toml file, main.py file which contains a simple example, and initializes a git repository with .gitignore file. This is cool!

Let's look at pyproject.toml file:

[project]
name = "myproject"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
A single file now manages your project's dependencies and configuration. But where is my virtual environment? Don't hurry, it will be generated once you add a new dependency:
uv add requests
This command installs the package into the virtual environment and updates pyproject.toml with the new dependency:
[project]
name = "uvtest"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = ["requests"]
In order to lock your dependencies, run uv lock:
uv lock
Now, your pyproject.toml file is ready for deployment. The locked dependencies ensure consistent environments across different users and machines.

Migrating from existing legacy projects

Existing legacy projects use conventional and raw pip with requirements.txt and maybe pip-tools with requirements.in. Anyways, uv is here to help you modernize your Python tooling. You can migrate your projects step-by-step using the following steps:

Migration Steps

  1. Open the existing project folder and initialize uv:
    This command will create the uv project structure for you. It won’t overwrite the main.py file if you have one, but it’ll create the file if it’s missing. It neither modifies your Git repository nor your README.md file.
    uv init
    
    However, this command won’t work if you already have a pyproject.toml file in place. If that’s the case, then you can move the file to another location and run the uv init command. Finally, you can update the new file with any relevant configuration from your old pyproject.toml.
  2. Export existing dependencies:
    In order to synchronize the current dependencies with uv, we have to first export them to a requirements.txt file if not already done:
    pip freeze > requirements.txt
    
  3. Add dependencies using uv:
    The following command adds all dependencies to uv which then automatically updates the pyproject.toml file:
    uv add -r requirements.txt
    
    This command installs all dependencies listed in requirements.txt, just like we did pip install -r requirements.txt, but much faster.
  4. Lock dependencies:
    The following command locks the dependencies in the pyproject.toml file:
    uv lock
    
    Locking dependencies ensure that this setup works everywhere, no matter what machine. Because it stores the whole dependency tree with exact versions.

Success

That's it! You now have a modern Python tooling setup that is easy to use and maintain.

If you want to remove a dependency, you can use the uv remove command:

uv remove requests
And if you want to update a dependency, you can use the uv update command:
uv update requests

Managing Dev and Prod Dependencies

In most development environments, you’ll have dependencies that aren’t required for running the code but are vital for developing it. For example, testing libraries like pytest, code formatters like Ruff, and static type checkers like mypy might be some of these development dependencies.

The following command adds the development dependency as a separate group:

uv add --dev pytest
If you check the content of pyproject.toml after running the command above, then you’ll find the following:
[project]
name = "myproject"
version = "0.1.0"
description = "My Project Description"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
    "requests>=2.32.3",
]

[dependency-groups]
dev = [
    "pytest>=8.3.5",
]
This ensures whenever a new developer joins your team, he simply installs all development dependencies directly. And while in server, you may install only production dependencies in the same way.

Locking and Syncing

We have already seen that the uv lock command locks the dependencies in the pyproject.toml file to a separate file called uv.lock. This file is used by the uv sync command to synchronize the dependencies in the pyproject.toml file with the ones in the uv.lock file. It helps to new developers to have a consistent, synchronized setup.

So, a new developer just does:

uv sync
And then they can start working on the project.

Managing Multiple Python Versions

uv installs Python and allows quickly switching between versions. You can install multiple versions of python using the following command:

uv python install 3.10 3.11 3.12

Create a new virtual environment using a specific Python version:

uv venv --python 3.10

You can also pin a specific Python version in the current directory:

uv python pin 3.10

You can find more here.

Conclusion: Commands we used so far

  • uv init: Initialize a new uv project
  • uv add: Add a dependency to the project
  • uv remove: Remove a dependency from the project
  • uv update: Update a dependency in the project
  • uv lock: Lock the dependencies in the project
  • uv sync: Synchronize the dependencies in the project with the ones in the lock file
  • uv self upgrade: Upgrade uv to the latest version
  • uv python install: Install a specific Python version
  • uv python pin: Pin a specific Python version in the current directory
  • uv venv: Create a new virtual environment
  • uv run: Run a command in a virtual environment

Ruff: The Fast Python Linter & Formatter

ruff is a fast, Rust-based linter and formatter for Python that replaces tools like: - flake8 - isort - pylint - pycodestyle - black

It supports over 500 rules and is highly configurable.

The recommended way to install ruff and integrate to your project is using uv:

uv add --dev ruff
# or, if you want it to install user-wide in your system
uv tool install ruff

Using ruff is easy. To check for formatting and linting errors, just run:

ruff check
# or if you want to fix fixable errors along the way:
ruff check --fix

The check command can generate several errors with vague messages. To get more information, you can find the ruff rule description:

ruff rule F821

Continuous Linting. To have continuous linting as you code, open a new terminal window and pass the --watch flag to the check command:

ruff check --watch
This will watch for any linting errors as you write your code, in real time!

Formatting Python Code. By default, Ruff has sensible formatting rules and was designed to be a drop-in replacement for Black. The following command automatically formats your code on desired directory (current directory if not specified):

ruff format
Just like the check command, the format command takes optional arguments for a path to a single file or directory.

Configuring Ruff

We need a configuration file to manage Ruff's rules. There are 2 options for us: - separate ruff.toml file - ruff config directly in pyproject.toml

Let's consider a sample configuration file ruff.toml:

[tool.ruff]
line-length = 79    # PEP 8 recommended line length
select = ["E", "F", "B", "I"]  # Equivalent to flake8 + isort + bugbear
ignore = ["E203", "E266", "E501", "W503", "F403", "F401"]  # From our old flake8 config
exclude = [
  ".git", ".mypy_cache", ".pytest_cache", ".tox", ".venv", "venv", ".env", "env",
  ".vscode", "static", "media", "templates", "developments", "scripts",
  "requirements", "*/templates/*", "*/migrations/*"
]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"
A configuration file for ruff is essential for standardizing code style and enforcing consistent rules across a project. Whether you use a standalone ruff.toml or integrate it into pyproject.toml, the setup allows you to precisely control linting rules, ignore specific checks, and exclude certain directories. This helps maintain a clean and consistent codebase, improving readability and collaboration.


References