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
# 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 --version3. Creating a New Project
Setting Up a Basic Project Structure
# 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\activateCreate a basic project structure:
# 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.mdYour 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:
[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
# 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 mypyInstalling from pyproject.toml
# Install all dependencies from pyproject.toml
uv pip sync
# Install with development dependencies
uv pip sync --all-extrasUpgrading Dependencies
# Upgrade a specific package
uv pip install --upgrade requests
# Upgrade all packages
uv pip install --upgrade-allUsing a requirements.txt File
# 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.lock5. Virtual Environment Management
Creating Virtual Environments
# Create a virtual environment
uv venv
# Create with specific Python version
uv venv --python=python3.9
# Create in specific location
uv venv /path/to/venvActivating and Deactivating
# Activate (Linux/macOS)
source .venv/bin/activate
# Activate (Windows)
# .venv\Scripts\activate
# Deactivate
deactivateRunning Commands in the Environment
# Run Python script in virtual environment
uv run python main.py
# Run other commands
uv run pytest6. Project Development
Basic Example Code
Create a simple module in my_uv_project/main.py:
"""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:
[project.scripts]
my-uv-command = "my_uv_project.main:main"Writing Tests
Create a test file in tests/test_main.py:
"""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:
[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 = trueRunning Code Quality Tools
# 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_project8. Documentation
Setting Up Documentation with MkDocs
# Install MkDocs
uv pip install --add-dev mkdocs mkdocs-material
# Initialize documentation
uv run mkdocs new .Create a basic mkdocs.yml configuration:
site_name: My uv Project
theme:
name: material
nav:
- Home: index.md
- Installation: installation.md
- Usage: usage.md
- API Reference: api.md
plugins:
- searchBuilding Documentation
# Build documentation
uv run mkdocs build
# Serve documentation locally
uv run mkdocs serve9. 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:
# 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:
[distutils]
index-servers =
pypi
testpypi
[pypi]
username = __token__
password = pypi-xxx
[testpypi]
username = __token__
password = testpypi-xxxPublishing to PyPI
# 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:
# 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.012. Continuous Integration
Example GitHub Actions Workflow
Create .github/workflows/python-package.yml:
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 pytest13. Advanced uv Usage
Benchmarking
uv is known for its speed. You can compare its performance against pip:
# Time pip install
time pip install requests
# Time uv install
time uv pip install requestsCaching Behavior
uv has smart caching:
# Clear cache
uv cache clear
# Show cache info
uv cache infoUsing uv with requirements.txt and lockfiles
# Generate a lockfile from requirements.txt
uv pip compile requirements.txt -o requirements.lock
# Install from lockfile
uv pip sync requirements.lockCreating a Self-Contained Application
# 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.sh14. Best Practices with uv
- Use lockfiles: Create
requirements.lockfiles for reproducible builds - Leverage uv's speed: Use it in CI/CD to speed up workflows
- Organize dependencies: Separate prod and dev dependencies
- Automate testing: Set up a CI pipeline with your test suite
- Document your API: Use tools like MkDocs with mkdocstrings
- Type annotations: Add type hints and validate with mypy
- Consistent formatting: Use Black and Ruff for automated code quality
- Follow semantic versioning: Use major.minor.patch format
- Keep dependencies minimal: Only add what you need
- 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.