UV and Ruff: Turbocharging Python Development with Rust-Powered Tools
Python’s a powerhouse, but let’s be honest, managing code quality and dependencies can feel like herding cats. Linting, formatting, and package management often involve juggling multiple tools, each with its own quirks. Enter UV and Ruff: two Rust-powered tools that streamline these tasks with blazing speed and simplicity. This post dives deep into how they work, why they’re game-changers, and how to use them effectively in your Python projects.
- Why UV and Ruff? They’re fast, unified, and built in Rust for performance.
- What’s the goal? Show you how to integrate these tools into your workflow with practical, code-heavy examples.
What is uv?
uv is a Rust-based Python package and project manager that replaces tools like pip, virtualenv, Poetry, and pipenv. Built by Astral (the same team behind Ruff), uv prioritizes speed and simplicity. It handles everything from creating projects to resolving dependencies and running scripts, all while being orders of magnitude faster than Python-based alternatives. Its tool runner (uv tool run) and lockfile support make it a one-stop shop for Python project management.
Key Features:
- Resolves dependencies in milliseconds using a SAT solver.
- Automatically manages virtual environments, no manual activation needed.
- Runs external tools (like Ruff) via uv tool run.
- Generates lockfiles for reproducible builds.
Benefits:
- Speeds up project setup and dependency installation.
- Handles complex dependency graphs with ease.
- Integrates tightly with Ruff for a unified workflow.
Why is uv Soooo Fast?
Like Ruff, uv benefits from Rust’s compiled nature and multithreading capabilities. Dependency resolution, a notorious bottleneck in tools like pip or Poetry, is handled by a SAT solver that runs in parallel, evaluating package constraints efficiently. Python-based tools, hampered by the GIL, process dependencies sequentially, leading to slowdowns on large projects. uv also caches packages globally, so reinstalls are nearly instantaneous, unlike pip’s repetitive downloads.
Example of Setting Up a Project with uv:
Let’s create a new project from scratch:
uv init my-project cd my-project
Add some dependencies, like requests and pandas:
uv add requests pandas
This creates a virtual environment and updates pyproject.toml:
[project] name = "my-project" version = "0.1.0" dependencies = [ "requests>=2.28.0", "pandas>=2.0.0", ]
To run a script, say main.py , without activating the virtual environment:
uv run python main.py
uv’s dependency resolution takes milliseconds, even for projects with dozens of packages. Compare that to pip, which can take seconds, or Poetry, which might crawl on complex dependency trees. The global cache means subsequent installs are virtually free, saving you time on CI or local setups.
What is Ruff?
Ruff is a Python linter and formatter written in Rust, designed to replace a slew of tools like Flake8, Pylint, isort, and Black. It’s not just fast, it’s blazingly fast, often 10-100x quicker than its Python-based counterparts. Ruff combines linting and formatting into a single tool, supports auto-fixing issues, and offers over 700 configurable rules. Whether you’re catching syntax errors, enforcing style guidelines, or tidying up imports, Ruff does it all in one pass, making it a powerhouse for maintaining code quality.
Key Features:
- Combines linting and formatting, reducing tool sprawl.
- Supports over 700 rules, including compatibility with Flake8 plugins.
- Auto-fixes issues like unused imports, missing commas, or incorrect syntax.
- Integrates seamlessly with IDEs like VS Code, PyCharm, and CI pipelines.
Benefits:
- Simplifies setup by replacing multiple tools with one.
- Scales effortlessly for large codebases (100k+ lines of code).
- Requires minimal configuration to get started.
Why is Ruff So Fast?
Ruff’s speed comes from Rust, a compiled language that produces highly optimized machine code. Unlike Python, which is interpreted and slowed by the Global Interpreter Lock (GIL), Rust supports multithreading, allowing Ruff to parallelize tasks across CPU cores. For example, linting thousands of files can be split into concurrent jobs, slashing processing time. Additionally, Rust’s memory safety eliminates the overhead of Python’s garbage collection, ensuring Ruff runs lean and mean.
Example of Linting and Formatting with Ruff
Install Ruff globally to enable system-wide usage using UV
uv tool install ruff
Imagine you’ve got a sloppy Python script, sample.py, with some common issues:
# sample.py def greet(name): print(f"hello {name}") # Missing comma, unformatted unused_var = 42 return None
To catch these problems, run Ruff’s linter:
ruff check sample.py
Output:
sample.py:2:5: E703 Missing comma in f-string
sample.py:3:5: F841 Local variable `unused_var` is assigned to but never used
Found 2 errors (1 fixable).
Ruff pinpoints the missing comma and unused variable. To fix the fixable issue automatically:
ruff check sample.py --fix
Then, format the code to adhere to PEP 8 style guidelines:
ruff format sample.py
The updated sample.py looks clean:
def greet(name): print(f"hello, {name}") return None
This process is lightning-fast because Ruff’s Rust backend processes the abstract syntax tree (AST) in parallel, unlike Flake8 or Pylint, which are constrained by Python’s single-threaded execution.
Why Rust for Python Tools?
Rust is the backbone of UV and Ruff’s performance. Here’s why it outshines Python for these tools:
- Compiled vs. Interpreted: Rust compiles to machine code, executing directly on the CPU. Python, being interpreted, incurs overhead as the interpreter translates code at runtime.
- Multithreading: Rust’s ownership model enables safe, concurrent execution across multiple threads. Python’s GIL forces single-threaded execution, bottlenecking tools like Flake8 or pip.
- Memory Efficiency: Rust’s zero-cost abstractions and lack of garbage collection minimize memory overhead, unlike Python’s dynamic memory management.
- Parallel Processing: Both UV and Ruff leverage Rust’s parallelism to process files or resolve dependencies concurrently, scaling with CPU cores.
The Python community is rallying around these tools. X posts and tech blogs highlight their adoption in projects like Django, FastAPI, and even internal tools at companies like Microsoft. Developers love the speed and simplicity, and the Rust-Python synergy is proving to be a winning formula.
Comparing UV and Ruff to Traditional Tools
| Tool | Speed | Features | Integration | | ---------- | ------------------------------------- | ------------------------------------------ | ----------------------- | | Ruff | 10-100x faster than Flake8/Pylint | Linting + formatting, 700+ rules | VS Code, GitHub Actions | | Flake8 | Slow on large codebases | Linting only, plugin ecosystem | Similar IDE support | | Black | Moderate speed | Formatting only | IDEs, CI/CD | | uv | Resolves dependencies in milliseconds | Package & Project management, lockfiles | Seamless with Ruff | | pip | Slower resolution | Package management only | Manual venv handling | | Poetry | Slow for large projects | Package + project management | Limited tool runner | | pipenv | Slow resolution, heavy setup | Package + virtualenv management, lockfiles | Clunky CLI, IDE support |
- Ruff: Replaces multiple tools, runs faster, and scales better. Flake8 and Pylint struggle with large codebases due to Python’s GIL, while Black is limited to formatting.
- uv: Outpaces pip, Poetry, and pipenv in dependency resolution and project setup. pipenv’s slow resolution and cumbersome CLI make it less appealing for modern workflows.
- Integration: UV and Ruff shine in IDEs and CI/CD, with uv’s tool runner bridging the gap between project management and linting.
pipenv, while popular for its lockfile support, suffers from slow dependency resolution and a heavy virtualenv setup. Its Python-based resolver can’t match uv’s Rust-powered SAT solver, and its CLI feels clunky compared to uv’s streamlined commands.
Practical Example: Using UV and Ruff Together
Let’s walk through building a small API client to fetch data from a REST endpoint. This example shows how UV and Ruff streamline real-world development.
Step 1: Set Up the Project
uv init api-client cd api-client uv add requests httpx # add Ruff to your project. uv add --dev ruff
This sets up a virtual environment and adds requests and httpx to pyproject.toml.
[project] name = "api-client" version = "0.1.0" dependencies = [ "requests>=2.28.0", "httpx>=0.23.0", ]
uv’s SAT solver resolves these dependencies in milliseconds, and the global cache ensures future installs are even faster.
Step 2: Write the Code
Create main.py :
import requests import httpx def fetch_data(url): response = requests.get(url) # Unhandled exception data = response.json() print(data) def main(): fetch_data("https://fake-json-api.mock.beeceptor.com/companies") if __name__ == "__main__": main()
This code fetches JSON data but lacks error handling and uses a vague variable name. Check the code here
Step 3: Lint with Ruff
Run Ruff to catch issues:
uv run ruff check main.py
Output:
main.py:5:5: E741 Unhandled exception: `requests.RequestException`
main.py:6:5: E741 Ambiguous variable name: `data`
Found 2 errors (1 fixable).
Fix the exception handling:
uv run ruff check main.py --fix
Updated main.py:
import requests import httpx def fetch_data(url): try: response = requests.get(url) result = response.json() print(result) except requests.RequestException as e: print(f"Error: {e}") def main(): fetch_data("https://fake-json-api.mock.beeceptor.com/companies") if __name__ == "__main__": main()
Ruff’s Rust backend makes this process near-instant, catching errors and formatting code in one pass.
Format the code:
uv run ruff format main.py
Step 4: Run with uv
Execute the script:
uv run main.py
uv handles the virtual environment and dependencies transparently, so you can focus on coding.
To try httpx instead, update main.py :
import httpx def fetch_data(url): try: response = httpx.get(url) result = response.json() print(result) except httpx.RequestException as e: print(f"Error: {e}") def main(): fetch_data("https://fake-json-api.mock.beeceptor.com/companies") if __name__ == "__main__": main()
This workflow - setting up, coding, linting and formatting is smooth and fast. uv manages dependencies and scripts, while Ruff ensures code quality, all with minimal overhead.
Integrations
Both Ruff offers Visual Studio Code extensions that help you identify issues in your code in real time and allow you to use Ruff as a formatter instead of VSCode's default formatter.
You can install Ruff extension and add to .vscode/settings.json
{ "python.linting.enabled": true, "python.linting.ruffEnabled": true, "python.formatting.provider": "ruff" }
For GitHub Actions, add a workflow:
name: Lint and Format on: [push] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install uv run: curl -LsSf https://astral.sh/uv/install.sh | sh - name: Install Ruff run: uv tool install ruff - name: Lint run: uv tool run ruff check . - name: Format run: uv tool run ruff format --check .
Advanced Usage:
Configuring Ruff
Create a ruff.toml for custom rules:
[lint] select = ["E", "F", "B", "I"] # Enable specific rule categories ignore = ["E501"] # Ignore line length warnings fixable = ["ALL"] # Auto-fix all fixable issues
Run Ruff with the config:
uv run ruff check . --config ruff.toml
Using uv’s Tool Runner
Run Ruff via uv:
uv tool run ruff check .
This ensures Ruff uses the project’s virtual environment, avoiding version conflicts.
Lockfiles with uv
Generate a uv.lock for reproducible builds:
uv lock
The lockfile pins exact dependency versions, ensuring consistency across environments, from local development to CI pipelines.
UV and Ruff are a dream team for Python developers. Ruff’s blazing-fast linting and formatting keep your code clean, while uv’s rapid dependency management and script execution simplify project setup. Their Rust foundation, compiled, multithreaded, and free from Python’s GIL, makes them orders of magnitude faster than tools like Flake8, pip, or pipenv. Try them out, tweak their configs, and see how they transform your workflow. The Python community’s buzzing about them on X, and Astral’s ongoing updates promise even more improvements. Dive in and experience the speed for yourself!
References and Links
- uv Documentation
- Ruff Documentation
- Demo Repo
- Ruff VSCode Extension - https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff
More Blog Posts You Might Find Useful
If you liked this blog post, here are a few more posts that might help you choose the right developer tools:
- Supabase vs. Clerk
- CodeRabbit vs. Other AI Code Review Tools
- Neon vs. Supabase
- MongoDB vs. PostgreSQL
- Cody vs. Cursor
- State of Databases for Serverless in 2024
- Cursor vs Windsurf
Check them out.