Fast Python Package Management and Linting with uv and ruff
- 2025-03-14
- 🐍 Python
- Python Tools Performance Benchmark Setup Best Practices UV Ruff
0. Introduction: Virtual Environment and Linting Tools
When working with Python, managing dependencies and ensuring clean code are essential tasks. Over the years, the Python ecosystem has developed multiple tools for these purposes, but there is no universal agreement on which one to use. Tools like pipenv, venv, poetry, and conda all offer different ways to manage environments and dependencies, while linters like flake8, pylint, and black aim to enforce style and correctness.
Astral, the creators of ruff, have introduced a new tool called uv—a blazing-fast virtual environment and package manager built in Rust. Since both uv and ruff share the same goal of significantly improving Python workflows through speed and efficiency, this post will explore both tools and why they might be the best options for modern Python development.
1. Why uv is a Game-Changer
I previously recommended poetry for dependency management (see Managing Package Versions with Poetry), but
uv has since proven to be a superior choice. Unlike poetry, which is written in Python, uv is built with Rust, making it significantly faster. It offers a simple interface while maintaining compatibility with standard pip and venv workflows.
1.1. Installing uv
To install uv, simply run:
pip install uv
Alternatively, you can use:
curl -LsSf https://astral.sh/uv/install.sh | sh
Or, if using brew on macOS:
brew install astral-sh/uv/uv
1.2. Single-Script Execution and Ad-Hoc Dependencies
One of uv’s standout features is the ability to execute scripts with dependencies without manually managing a virtual environment:
uv run --with rich example.py
You can also add dependencies dynamically to a script:
uv add --script example.py 'requests<3' 'rich'
More info at Declaring script dependencies | Astral uv
This makes uv an excellent choice for quick experiments or one-off scripts that need isolated dependencies.
1.3. Initializing a Project
To create a new Python project with uv, run:
uv init
This will prompt you to enter project details like name, version, and dependencies. It will create pyproject.toml and .python-version.
1.4. Creating a Virtual Environment
A major feature of uv is its fast virtual environment management:
uv venv .venv
You can also specify a Python version:
uv venv --python=3.11 .venv
If a .python-version file exists, uv will automatically use the specified version.
1.5. Installing Dependencies
Install packages like you would with pip:
uv pip install requests pandas
To install packages in editable mode (useful for development environments and CI/CD):
uv pip install --editable .
This ensures your local code changes reflect immediately when running scripts.
1.6. Running Commands in an Isolated Environment
Unlike traditional virtual environment tools, uv lets you run commands directly without needing to activate the environment:
uv run python script.py
uv run pytest
1.7. Handling Editable Installs and Running a Package
One issue many users face with uv is running a local package. If you are working with a structured project (e.g., using src/ layout), the correct way to install and execute it is:
uv pip install --editable .
uv run python -m src.main
This ensures that the installed package is properly recognized when executing it as a module.
2. ruff: The Ultra-Fast Linter and Formatter
Linters and formatters are essential for maintaining clean and readable code. Traditionally, flake8, black, and pylint have been widely used, but they are slow due to being written in Python.
Enter ruff, a Rust-based linter that combines linting, formatting, and import sorting into a single, high-performance tool. It serves as a replacement for flake8, black, isort, and even mypy-style type checking in some cases.
2.1. Installing ruff
To install ruff, use:
pip install ruff
Or, for even better performance, use uv:
uv pip install ruff
2.2. Running ruff
To check your Python files, simply run:
ruff check .
For formatting, use:
ruff format .
This command is a drop-in replacement for black but executes significantly faster.
3. CI/CD Integration
Here you have some common recipies for integrating uv and ruff with CI/CD workflows.
3.1. Running ruff with pre-commit
For CI/CD workflows, you can use pre-commit with:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.6
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
3.2. Exporting uv Dependencies
In some setups, you might need a requirements.txt. For example, when working with Prefect it expects that file.
To generate the requirements.txt file run:
uv export --format requirements-txt > requirements.txt
For CI/CD workflows, you can use pre-commit to ensure dependencies are always up-to-date:
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.6.3
hooks:
- id: uv-export
args: ["--no-hashes", "--frozen", "--output-file=requirements.txt"]
3.3. Running Tests with uv
To run tests inside the virtual environment:
uv run pytest # Or any other testing suite you use
For GitHub Actions:
unit_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: Install UV
run: pip install uv
- name: Set up virtual environment
run: uv venv .venv
- name: Install dependencies
run: uv pip install --editable .
- name: Run tests
run: uv run pytest .3.4. Publishing to PyPI with uv
To build and publish a package to PyPI using uv:
uv build
uv publish
For automation, add this to GitHub Actions:
jobs:
publish_pypi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
- name: Install uv
run: pip install uv
- name: Build
run: uv build
- name: Publish
run: uv publishI strongly suggest you set up Trusted publishers in pypi to avoid adding tokens.
It’s the prefered way of uploading packages to pypi.
4. Benchmarks: How Fast Are uv and ruff?
Rust-based tooling provides substantial speed improvements over Python-based alternatives. The following benchmarks are sourced from the Official Astral documentation:
4.1. uv benchmarks
At uv benchmarks you can see a very comprehensive speed analysis.
For quick reference here is a summary:
4.2. ruff benchmarks
Here you can see the time it took to format Zulip codebase (~ 250k lines of code).
More info at Ruff formatter .
5. Conclusion
Astral’s tools, uv and ruff, provide a powerful, high-performance alternative to traditional Python package management and linting solutions. By leveraging Rust’s efficiency, they dramatically reduce execution time while maintaining full compatibility with existing workflows.
If you’re looking for a faster, smoother experience in Python development, consider replacing poetry with uv and switching from flake8 + black to ruff. The time savings alone make them well worth the transition.
To get started, try installing uv and ruff today, and experience the speed difference firsthand!