Skip to content

Comprehensive Guide to Python Project Development with uv

This guide demonstrates how to create, develop, and publish a Python project using uv—a fast and reliable Python packaging tool designed as a replacement for pip, virtualenv, and similar tools.

1. Introduction to uv

uv is an extremely fast Python package installer and resolver, built in Rust. It serves as a drop-in replacement for pip, virtualenv, and other Python packaging tools with significant performance benefits.

Why Choose uv?

  • Blazing fast performance: 10-100x faster than traditional tools
  • Reliability: Robust dependency resolution
  • Compatibility: Works as a drop-in replacement for pip and other tools
  • Modern features: Includes lockfile support, advanced caching, and more
  • Unified tool: Combines multiple packaging utilities into one tool

2. Installation

Installing uv

bash
# Install using pip (ironically)
pip install uv

# Alternative: install using curl (Linux/macOS)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Verify installation
uv --version

3. Creating a New Project

Setting Up a Basic Project Structure

bash
# Create project directory
mkdir my-uv-project
cd my-uv-project

# Create a virtual environment with uv
uv venv

# Activate the virtual environment
# On Linux/macOS:
source .venv/bin/activate
# On Windows:
# .venv\Scripts\activate

Create a basic project structure:

bash
# Create the project structure
mkdir -p my_uv_project/tests docs
touch my_uv_project/__init__.py
touch my_uv_project/main.py
touch tests/__init__.py
touch README.md

Your project structure should look like this:

my-uv-project/
├── .venv/                # Virtual environment
├── my_uv_project/        # Source code package
│   ├── __init__.py       # Package initialization
│   └── main.py           # Main module
├── tests/                # Test directory
│   └── __init__.py       # Test initialization
├── README.md             # Project documentation
└── pyproject.toml        # Project configuration (we'll create this next)

Creating pyproject.toml

Create a pyproject.toml file for your project:

toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-uv-project"
version = "0.1.0"
description = "A sample project using uv"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "your.email@example.com"},
]
dependencies = []

[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "black>=23.0.0",
    "ruff>=0.0.269",
    "mypy>=1.0.0",
]

[project.urls]
Homepage = "https://github.com/yourusername/my-uv-project"
Issues = "https://github.com/yourusername/my-uv-project/issues"

[tool.hatch.build.targets.wheel]
packages = ["my_uv_project"]

4. Managing Dependencies

Installing Dependencies

bash
# Install a package and add to pyproject.toml
uv pip install --add requests

# Install multiple packages
uv pip install --add "pandas>=2.0.0" numpy matplotlib

# Install development dependencies
uv pip install --add-dev pytest black ruff mypy

Installing from pyproject.toml

bash
# Install all dependencies from pyproject.toml
uv pip sync

# Install with development dependencies
uv pip sync --all-extras

Upgrading Dependencies

bash
# Upgrade a specific package
uv pip install --upgrade requests

# Upgrade all packages
uv pip install --upgrade-all

Using a requirements.txt File

bash
# Create requirements.txt
echo "requests>=2.28.0" > requirements.txt
echo "flask>=2.0.0" >> requirements.txt

# Install from requirements.txt
uv pip install -r requirements.txt

# Create a lockfile for reproducible builds
uv pip freeze > requirements.lock

5. Virtual Environment Management

Creating Virtual Environments

bash
# Create a virtual environment
uv venv

# Create with specific Python version
uv venv --python=python3.9

# Create in specific location
uv venv /path/to/venv

Activating and Deactivating

bash
# Activate (Linux/macOS)
source .venv/bin/activate

# Activate (Windows)
# .venv\Scripts\activate

# Deactivate
deactivate

Running Commands in the Environment

bash
# Run Python script in virtual environment
uv run python main.py

# Run other commands
uv run pytest

6. Project Development

Basic Example Code

Create a simple module in my_uv_project/main.py:

python
"""Main module for my_uv_project."""

def hello(name="World"):
    """Return a greeting message.

    Args:
        name: Name to greet (default: "World")

    Returns:
        str: A greeting message
    """
    return f"Hello, {name}!"

def main():
    """Entry point for the application."""
    print(hello())

if __name__ == "__main__":
    main()

Create an Entry Point

Update your pyproject.toml to include a CLI entry point:

toml
[project.scripts]
my-uv-command = "my_uv_project.main:main"

Writing Tests

Create a test file in tests/test_main.py:

python
"""Tests for the main module."""

from my_uv_project.main import hello

def test_hello_default():
    """Test the default greeting."""
    assert hello() == "Hello, World!"

def test_hello_custom():
    """Test a custom greeting."""
    assert hello("uv") == "Hello, uv!"

7. Code Quality Tools

Configure Linting and Formatting

Add configuration for Black and Ruff to your pyproject.toml:

toml
[tool.black]
line-length = 88
target-version = ["py38", "py39", "py310"]
include = '\.pyi?$'

[tool.ruff]
line-length = 88
target-version = "py38"
select = ["E", "F", "B", "I", "N", "PL"]
ignore = []

[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true

Running Code Quality Tools

bash
# Format code with Black
uv run black my_uv_project tests

# Lint with Ruff
uv run ruff check my_uv_project tests

# Type check with mypy
uv run mypy my_uv_project

8. Documentation

Setting Up Documentation with MkDocs

bash
# Install MkDocs
uv pip install --add-dev mkdocs mkdocs-material

# Initialize documentation
uv run mkdocs new .

Create a basic mkdocs.yml configuration:

yaml
site_name: My uv Project
theme:
  name: material

nav:
  - Home: index.md
  - Installation: installation.md
  - Usage: usage.md
  - API Reference: api.md

plugins:
  - search

Building Documentation

bash
# Build documentation
uv run mkdocs build

# Serve documentation locally
uv run mkdocs serve

9. Building Your Package

To build your Python package, you'll need a build tool like Hatch, Build, or Setuptools. Since we configured Hatch in our pyproject.toml:

bash
# Install hatch
uv pip install --add-dev hatch

# Build the package
uv run hatch build

# This creates distribution files in dist/
# - my_uv_project-0.1.0.tar.gz (source)
# - my_uv_project-0.1.0-py3-none-any.whl (wheel)

10. Publishing Your Package

Configuring PyPI Credentials

Create a ~/.pypirc file:

ini
[distutils]
index-servers =
    pypi
    testpypi

[pypi]
username = __token__
password = pypi-xxx

[testpypi]
username = __token__
password = testpypi-xxx

Publishing to PyPI

bash
# Install twine
uv pip install --add-dev twine

# Check your package
uv run twine check dist/*

# Upload to TestPyPI first (good practice)
uv run twine upload --repository testpypi dist/*

# Upload to PyPI
uv run twine upload dist/*

11. Version Management

Unlike Poetry, uv doesn't have built-in version management commands. You'll need to manually update the version in pyproject.toml.

For a more automated approach, you can use a tool like bump2version:

bash
# Install bump2version
uv pip install --add-dev bump2version

# Configure .bumpversion.cfg
cat > .bumpversion.cfg << EOL
[bumpversion]
current_version = 0.1.0
commit = True
tag = True

[bumpversion:file:pyproject.toml]
search = version = "{current_version}"
replace = version = "{new_version}"
EOL

# Bump version
uv run bump2version patch  # 0.1.0 -> 0.1.1
uv run bump2version minor  # 0.1.1 -> 0.2.0
uv run bump2version major  # 0.2.0 -> 1.0.0

12. Continuous Integration

Example GitHub Actions Workflow

Create .github/workflows/python-package.yml:

yaml
name: Python Package

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.8, 3.9, "3.10"]

    steps:
      - uses: actions/checkout@v3

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}

      - name: Install uv
        run: pip install uv

      - name: Create venv and install dependencies
        run: |
          uv venv
          source .venv/bin/activate
          uv pip sync --all-extras

      - name: Lint with Ruff
        run: |
          source .venv/bin/activate
          uv run ruff check .

      - name: Format check with Black
        run: |
          source .venv/bin/activate
          uv run black --check .

      - name: Type check with mypy
        run: |
          source .venv/bin/activate
          uv run mypy my_uv_project

      - name: Test with pytest
        run: |
          source .venv/bin/activate
          uv run pytest

13. Advanced uv Usage

Benchmarking

uv is known for its speed. You can compare its performance against pip:

bash
# Time pip install
time pip install requests

# Time uv install
time uv pip install requests

Caching Behavior

uv has smart caching:

bash
# Clear cache
uv cache clear

# Show cache info
uv cache info

Using uv with requirements.txt and lockfiles

bash
# Generate a lockfile from requirements.txt
uv pip compile requirements.txt -o requirements.lock

# Install from lockfile
uv pip sync requirements.lock

Creating a Self-Contained Application

bash
# Create an app directory
mkdir -p app
cd app

# Create a virtual environment in the app directory
uv venv .venv

# Install dependencies
uv pip install flask gunicorn

# Create a minimal app
cat > app.py << EOL
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello from uv-powered app!"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)
EOL

# Create a run script
cat > run.sh << EOL
#!/bin/bash
source .venv/bin/activate
gunicorn -b 0.0.0.0:8080 app:app
EOL

# Make it executable
chmod +x run.sh

14. Best Practices with uv

  1. Use lockfiles: Create requirements.lock files for reproducible builds
  2. Leverage uv's speed: Use it in CI/CD to speed up workflows
  3. Organize dependencies: Separate prod and dev dependencies
  4. Automate testing: Set up a CI pipeline with your test suite
  5. Document your API: Use tools like MkDocs with mkdocstrings
  6. Type annotations: Add type hints and validate with mypy
  7. Consistent formatting: Use Black and Ruff for automated code quality
  8. Follow semantic versioning: Use major.minor.patch format
  9. Keep dependencies minimal: Only add what you need
  10. Use .gitignore: Exclude virtual environments and build artifacts

15. uv vs. Other Tools

uv vs. pip

  • uv is significantly faster than pip
  • uv has built-in virtual environment management
  • uv can generate lockfiles for reproducible builds

uv vs. Poetry

  • Poetry has more built-in project management features
  • uv is faster for dependency installation
  • Poetry has better version management commands
  • uv works with traditional requirements.txt files

uv vs. Conda

  • Conda manages non-Python dependencies and whole environments
  • uv is focused on Python packages only but with better performance
  • Conda has more extensive binary package support

Conclusion

uv represents the next generation of Python package management tools, focusing on speed and reliability while offering a familiar interface compatible with existing workflows. By following this guide, you've learned how to set up a complete Python project with uv, from initialization to testing, building, and publishing.

Whether you're building a small utility or a large application, uv can help streamline your development process with its blazing fast performance.