16 KiB
16 KiB
Advanced Workflows with UV and Ruff
Comprehensive guide for advanced use cases, monorepos, Docker integration, and production deployments.
Table of Contents
- Monorepo Management
- Docker Integration
- CI/CD Pipelines
- Development Workflows
- Production Deployments
- Team Collaboration
Monorepo Management
Workspace Setup
UV supports Cargo-style workspaces for managing multiple packages in one repository.
Project Structure:
monorepo/
├── pyproject.toml # Workspace root
├── uv.lock # Shared lockfile
├── packages/
│ ├── core/
│ │ ├── pyproject.toml
│ │ └── src/
│ ├── api/
│ │ ├── pyproject.toml
│ │ └── src/
│ └── cli/
│ ├── pyproject.toml
│ └── src/
└── apps/
└── web/
├── pyproject.toml
└── src/
Root pyproject.toml:
[tool.uv.workspace]
members = [
"packages/*",
"apps/*"
]
[tool.uv]
dev-dependencies = [
"pytest>=7.0.0",
"ruff>=0.1.0",
]
Package pyproject.toml (core):
[project]
name = "myproject-core"
version = "0.1.0"
dependencies = [
"pydantic>=2.0.0",
]
[tool.uv.sources]
# No sources needed - uses workspace
Package pyproject.toml (api):
[project]
name = "myproject-api"
version = "0.1.0"
dependencies = [
"fastapi>=0.100.0",
"myproject-core", # Workspace dependency
]
[tool.uv.sources]
myproject-core = { workspace = true }
Working with Workspaces
# Install all workspace packages
uv sync
# Run commands from root
uv run --package myproject-api python -m uvicorn main:app
# Run tests for specific package
uv run --package myproject-core pytest
# Add dependency to specific package
cd packages/api
uv add requests
Shared Ruff Configuration
Root pyproject.toml:
[tool.ruff]
line-length = 88
target-version = "py311"
[tool.ruff.lint]
select = ["E", "W", "F", "I", "B"]
Per-package overrides:
# packages/cli/pyproject.toml
[tool.ruff.lint.per-file-ignores]
"src/cli/*.py" = ["T201"] # Allow prints in CLI
Monorepo Scripts
Makefile:
.PHONY: install lint format test
install:
\tuv sync
lint:
\truff check .
format:
\truff format .
test:
\tuv run pytest
# Per-package commands
test-core:
\tuv run --package myproject-core pytest
test-api:
\tuv run --package myproject-api pytest
Docker Integration
Multi-Stage Dockerfile
# syntax=docker/dockerfile:1
# Stage 1: Build stage with UV
FROM python:3.12-slim AS builder
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Set working directory
WORKDIR /app
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen --no-dev --no-cache
# Stage 2: Runtime stage
FROM python:3.12-slim
WORKDIR /app
# Copy virtual environment from builder
COPY --from=builder /app/.venv /app/.venv
# Copy application code
COPY . .
# Set PATH to use virtual environment
ENV PATH="/app/.venv/bin:$PATH"
# Run application
CMD ["python", "-m", "myapp"]
Development Dockerfile
FROM python:3.12-slim
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
# Copy dependency files
COPY pyproject.toml uv.lock ./
# Install all dependencies (including dev)
RUN uv sync --frozen --no-cache
# Copy code
COPY . .
# Development server
CMD ["uv", "run", "python", "-m", "uvicorn", "main:app", "--reload", "--host", "0.0.0.0"]
Docker Compose
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- uv-cache:/root/.cache/uv
ports:
- "8000:8000"
environment:
- PYTHONUNBUFFERED=1
command: uv run python -m uvicorn main:app --reload --host 0.0.0.0
worker:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/app
- uv-cache:/root/.cache/uv
command: uv run python -m celery worker
volumes:
uv-cache:
Optimized Production Dockerfile
FROM python:3.12-slim AS builder
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1
# Copy files
COPY pyproject.toml uv.lock ./
COPY src ./src
# Install dependencies and application
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev
FROM python:3.12-slim
WORKDIR /app
# Copy only necessary files
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/src /app/src
# Create non-root user
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "myapp"]
CI/CD Pipelines
GitHub Actions
Complete Workflow:
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install UV
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
run: uv sync --frozen
- name: Lint with Ruff
run: |
uv run ruff check .
uv run ruff format --check .
- name: Type check with mypy
run: uv run mypy src/
- name: Security check
run: uv run ruff check --select S .
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.11', '3.12']
steps:
- uses: actions/checkout@v4
- name: Install UV
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Set up Python
run: uv python install ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync --frozen
- name: Run tests
run: uv run pytest --cov=src --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
build:
needs: [quality, test]
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Install UV
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Build package
run: uv build
- name: Publish to PyPI
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: uv publish
GitLab CI
stages:
- lint
- test
- build
- deploy
variables:
UV_CACHE_DIR: ${CI_PROJECT_DIR}/.cache/uv
cache:
paths:
- .cache/uv
- .venv
before_script:
- curl -LsSf https://astral.sh/uv/install.sh | sh
- export PATH="$HOME/.local/bin:$PATH"
- uv sync --frozen
lint:
stage: lint
script:
- uv run ruff check .
- uv run ruff format --check .
test:
stage: test
parallel:
matrix:
- PYTHON_VERSION: ['3.11', '3.12']
script:
- uv python install $PYTHON_VERSION
- uv sync --frozen
- uv run pytest --cov=src
build:
stage: build
only:
- main
script:
- uv build
artifacts:
paths:
- dist/
deploy:
stage: deploy
only:
- main
script:
- uv publish --token $PYPI_TOKEN
Circle CI
version: 2.1
executors:
python-executor:
docker:
- image: python:3.12-slim
jobs:
lint:
executor: python-executor
steps:
- checkout
- run:
name: Install UV
command: curl -LsSf https://astral.sh/uv/install.sh | sh
- run:
name: Lint
command: |
export PATH="$HOME/.local/bin:$PATH"
uv sync --frozen
uv run ruff check .
uv run ruff format --check .
test:
executor: python-executor
steps:
- checkout
- run:
name: Install UV
command: curl -LsSf https://astral.sh/uv/install.sh | sh
- run:
name: Test
command: |
export PATH="$HOME/.local/bin:$PATH"
uv sync --frozen
uv run pytest
workflows:
main:
jobs:
- lint
- test
- deploy:
requires:
- lint
- test
filters:
branches:
only: main
Development Workflows
Pre-commit Integration
.pre-commit-config.yaml:
repos:
# Ruff linting and formatting
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.8
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format
# Type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-all]
# Security
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.8
hooks:
- id: ruff
name: ruff-security
args: [--select, S]
# Standard hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
Install hooks:
uv add --dev pre-commit
uv run pre-commit install
VS Code Integration
.vscode/settings.json:
{
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.ruff": "explicit",
"source.organizeImports.ruff": "explicit"
},
"editor.defaultFormatter": "charliermarsh.ruff"
},
"ruff.lint.args": ["--config=pyproject.toml"],
"ruff.format.args": ["--config=pyproject.toml"],
"python.testing.pytestEnabled": true,
"python.testing.pytestArgs": ["tests"]
}
.vscode/tasks.json:
{
"version": "2.0.0",
"tasks": [
{
"label": "UV: Sync",
"type": "shell",
"command": "uv sync",
"group": "build"
},
{
"label": "Ruff: Check",
"type": "shell",
"command": "uv run ruff check .",
"group": "test"
},
{
"label": "Ruff: Format",
"type": "shell",
"command": "uv run ruff format .",
"group": "build"
},
{
"label": "Test",
"type": "shell",
"command": "uv run pytest",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}
Development Scripts
justfile (like Makefile but better):
# Install dependencies
install:
uv sync
# Run development server
dev:
uv run python -m uvicorn main:app --reload
# Lint and format
lint:
uv run ruff check --fix .
uv run ruff format .
# Type check
typecheck:
uv run mypy src/
# Run tests
test:
uv run pytest -v
# Run tests with coverage
test-cov:
uv run pytest --cov=src --cov-report=html
# Security check
security:
uv run ruff check --select S .
# Update dependencies
update:
uv lock --upgrade
uv sync
# Clean caches
clean:
uv cache clean
ruff clean
find . -type d -name __pycache__ -exec rm -rf {} +
find . -type d -name .pytest_cache -exec rm -rf {} +
# All quality checks
check: lint typecheck security test
Production Deployments
AWS Lambda
FROM public.ecr.aws/lambda/python:3.12
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Copy application
COPY pyproject.toml uv.lock ./
COPY src ${LAMBDA_TASK_ROOT}/src
# Install dependencies
RUN uv sync --frozen --no-dev --no-cache
# Lambda handler
CMD ["src.handler.lambda_handler"]
Google Cloud Run
FROM python:3.12-slim
# Install UV
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev --no-cache
COPY . .
ENV PORT=8080
CMD exec uv run python -m uvicorn main:app --host 0.0.0.0 --port ${PORT}
Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8000
env:
- name: PYTHONUNBUFFERED
value: "1"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
Helm Chart
values.yaml:
image:
repository: myapp
tag: latest
pullPolicy: IfNotPresent
replicaCount: 3
service:
type: ClusterIP
port: 80
targetPort: 8000
env:
- name: PYTHONUNBUFFERED
value: "1"
- name: LOG_LEVEL
value: "info"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
Team Collaboration
Shared Configuration
pyproject.toml:
[project]
name = "myproject"
version = "0.1.0"
requires-python = ">=3.11"
[tool.uv]
# Shared dev dependencies
dev-dependencies = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"ruff>=0.1.0",
"mypy>=1.8.0",
]
[tool.ruff]
# Team-wide code style
line-length = 88
target-version = "py311"
[tool.ruff.lint]
# Agreed upon rules
select = ["E", "W", "F", "I", "B", "UP"]
ignore = []
[tool.ruff.lint.per-file-ignores]
# Consistent exceptions
"tests/*" = ["S101"]
"__init__.py" = ["F401"]
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
Code Review Checklist
CONTRIBUTING.md:
## Code Review Checklist
Before submitting a PR:
- [ ] Run `uv run ruff check --fix .`
- [ ] Run `uv run ruff format .`
- [ ] Run `uv run mypy src/`
- [ ] Run `uv run pytest`
- [ ] Update `uv.lock` if dependencies changed
- [ ] Add tests for new features
- [ ] Update documentation
- [ ] Ensure CI passes
Onboarding Guide
README.md:
## Getting Started
### Prerequisites
- Python 3.11+
- UV package manager
### Setup
1. Install UV:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
-
Clone repository:
git clone https://github.com/org/repo.git cd repo -
Install dependencies:
uv sync -
Run tests:
uv run pytest -
Start development server:
uv run python -m uvicorn main:app --reload
Development Commands
uv run ruff check --fix .- Lint codeuv run ruff format .- Format codeuv run pytest- Run testsuv run mypy src/- Type check
Adding Dependencies
uv add package-name
Updating Dependencies
uv lock --upgrade
uv sync
## Performance Optimization
### Caching Strategies
```bash
# Pre-warm cache in CI
- name: Cache UV
uses: actions/cache@v3
with:
path: ~/.cache/uv
key: ${{ runner.os }}-uv-${{ hashFiles('uv.lock') }}
# Use frozen lockfile for reproducibility
uv sync --frozen
# Offline mode for airgapped environments
uv sync --offline
Build Optimization
# Compile bytecode
export UV_COMPILE_BYTECODE=1
# Use system Python if available
export UV_SYSTEM_PYTHON=1
# Skip cache in CI
export UV_NO_CACHE=1
Troubleshooting
Common Issues
Slow dependency resolution:
# Use frozen lockfile
uv sync --frozen
# Clear cache
uv cache clean
Out of disk space:
# Prune old cache entries
uv cache prune
# Check cache size
du -sh ~/.cache/uv
Permission errors:
# Fix ownership
sudo chown -R $USER ~/.cache/uv
sudo chown -R $USER ~/.local/share/uv