-
Notifications
You must be signed in to change notification settings - Fork 18
Description
Problem
The project currently uses traditional Python dependency management tools (pip, requirements.txt). This leads to:
- Slower dependency resolution and installation times
- Inconsistent lock file formats
- Complex virtual environment management
- Slower CI/CD pipeline execution
- Tool lock-in: Using tool-specific configurations (like Poetry's
[tool.poetry]) makes it difficult to switch tools in the future
Modern Python projects benefit from faster, more reliable tooling. However, as discussed in #321, we should avoid vendor lock-in by using PEP 735 dependency groups - a Python standard that any compliant tool can use, not just one specific tool.
Proposed Solution
Migrate the FastAPI RESTful project to use uv as the primary dependency management tool, while using PEP 735 dependency groups for organizing dependencies. This approach provides:
- Speed up dependency installation (10-100x faster than pip)
- Provide a unified tool for virtual environment and dependency management
- Generate a reliable
uv.lockfile for reproducible builds - Improve developer experience with faster feedback loops
- Optimize CI/CD pipeline performance
- No vendor lock-in: PEP 735 is a Python standard that works with any compliant tool (uv today, pip in the future, and potentially Poetry when they add support)
This combines the performance benefits of uv with the flexibility of standards-based configuration.
Suggested Approach
1. Install uv locally for testing
curl -LsSf https://astral.sh/uv/install.sh | sh2. Create PEP 735-compliant pyproject.toml
Instead of using uv-specific or Poetry-specific configuration, use the standard PEP 735 format:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "fastapi-restful-sample"
version = "1.0.0"
description = "FastAPI RESTful API sample with SQLAlchemy"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.100.0",
"uvicorn[standard]>=0.23.0",
"sqlalchemy>=2.0.0",
"pydantic>=2.0.0",
"pydantic-settings>=2.0.0",
]
[dependency-groups]
test = [
"pytest>=7.4.0",
"pytest-cov>=4.1.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.24.0",
]
lint = [
"ruff>=0.1.0",
"mypy>=1.5.0",
]
dev = [
{include-group = "test"},
{include-group = "lint"},
]Key difference from original proposal: We're using [dependency-groups] (PEP 735 standard) instead of tool-specific sections. This means the same pyproject.toml will work with uv today and pip tomorrow, without modifications.
3. Initialize virtual environment and install dependencies
# Create virtual environment
uv venv
# Activate it
source .venv/bin/activate # Linux/macOS
# or
.venv\Scripts\activate # Windows
# Install project dependencies
uv pip install -e .
# Install with development dependencies
uv pip install -e . --group dev
# Or install specific dependency groups
uv pip install -e . --group test
uv pip install -e . --group lint4. Key files to modify
pyproject.toml
- Add
[project]section with core dependencies - Add
[dependency-groups]for test, lint, and dev groups (PEP 735 format) - Add
[build-system]configuration
.gitignore
# Virtual environment
.venv/
venv/
# uv
uv.lock
# Python
__pycache__/
*.py[cod]
*$py.classREADME.md
Update setup instructions:
## Setup
### Prerequisites
- Python 3.11+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
### Installation
1. Install uv:
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh-
Create virtual environment and install dependencies:
uv venv source .venv/bin/activate # Linux/macOS uv pip install -e . --group dev
-
Run the application:
uv run uvicorn main:app --reload
Running Tests
uv run pytestCode Quality
uv run ruff check .
uv run mypy .
#### `.github/workflows/python-app.yml`
Update the existing GitHub Actions workflow with the following changes:
```yaml
# Building and testing Python
# https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Python CI
permissions:
contents: read
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/[email protected]
- name: Lint commit messages
uses: wagoid/[email protected]
- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
- name: Set up Python
uses: actions/[email protected]
with:
python-version-file: '.python-version'
- name: Install dependencies
run: |
uv venv
uv pip install -e . --group lint
- name: Lint with Flake8
run: |
uv run flake8 .
- name: Check code formatting with Black
run: |
uv run black --check .
test:
needs: lint
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/[email protected]
- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"
- name: Set up Python
uses: actions/[email protected]
with:
python-version-file: '.python-version'
- name: Install dependencies
run: |
uv venv
uv pip install -e . --group test
- name: Run tests with pytest
run: |
uv run pytest -v
- name: Generate coverage report
run: |
uv run pytest --cov=./ --cov-report=xml --cov-report=term
- name: Upload coverage report artifact
uses: actions/[email protected]
with:
name: coverage.xml
path: ./coverage.xml
coverage:
needs: test
runs-on: ubuntu-latest
# Only run coverage for PRs from the same repository (not forks)
# This ensures secrets are available for Codecov and Codacy
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
strategy:
matrix:
service: [codecov, codacy]
steps:
- name: Checkout repository
uses: actions/[email protected]
- name: Download coverage report artifact
uses: actions/[email protected]
with:
name: coverage.xml
- name: Upload coverage report to ${{ matrix.service }}
if: ${{ matrix.service == 'codecov' }}
uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
- name: Upload coverage report to ${{ matrix.service }}
if: ${{ matrix.service == 'codacy' }}
uses: codacy/[email protected]
with:
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
coverage-reports: coverage.xml
container:
needs: coverage
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/[email protected]
- name: Log in to GitHub Container Registry
uses: docker/[email protected]
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/[email protected]
- name: Build and push Docker image to GitHub Container Registry
uses: docker/[email protected]
with:
context: .
push: true
platforms: linux/amd64
provenance: false
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:sha-${{ github.sha }}
Key changes:
- Removed
cache: 'pip'fromsetup-python(uv handles caching) - Added
astral-sh/setup-uv@v4action before Python setup in both lint and test jobs - Changed
pip install -r requirements-lint.txttouv venv && uv pip install -e . --group lint - Changed
pip install -r requirements-test.txttouv venv && uv pip install -e . --group test - Prefixed
flake8,black, andpytestcommands withuv run - Kept all existing workflow structure, job dependencies, and artifact handling unchanged
Files to remove
requirements.txtrequirements-lint.txtrequirements-test.txt
5. Common commands
# Environment management
uv venv # Create virtual environment
uv venv --python 3.12 # Create with specific Python version
# Dependency management
uv pip install -e . # Install project dependencies
uv pip install -e . --group dev # Install with dev dependencies
uv pip install -e . --group test # Install with test dependencies only
uv pip list # List installed packages
# Running commands
uv run uvicorn main:app --reload # Run application
uv run pytest # Run tests
uv run ruff check . # Run linter
uv run mypy . # Run type checkerAcceptance Criteria
-
uvis successfully installed and configured for the project -
pyproject.tomluses PEP 735[dependency-groups]format (not tool-specific configuration) - All project dependencies are defined in
[project.dependencies] - Development dependencies are organized in appropriate groups:
test,lint,dev - Virtual environment can be created with
uv venvand activated - Dependencies can be installed with
uv pip install -e . --group dev - Application runs successfully using
uv run uvicorn main:app --reload - All tests pass when executed via
uv run pytest - Linting works with
uv run ruff check .anduv run mypy . - CI/CD pipeline is updated to use uv and passes successfully
-
uv.lockfile is generated and committed to repository - README.md includes clear instructions for setting up the project with uv
- Old dependency management files are removed (
requirements*.txt) - Installation time is measurably faster than previous setup (document the improvement)
- Configuration is tool-agnostic and will work with pip when it adds PEP 735 support
References
- [PEP 735 – Dependency Groups in pyproject.toml](https://peps.python.org/pep-0735/) - Python standard for dependency groups
- [uv Documentation](https://docs.astral.sh/uv/)
- [uv PEP 735 Support](https://docs.astral.sh/uv/concepts/dependencies/#dependency-groups) - Confirms uv already supports the standard
- [uv GitHub Repository](https://github.com/astral-sh/uv)
- [GitHub Actions setup-uv](https://github.com/astral-sh/setup-uv)
- [uv Blog Post - "uv: Python packaging in Rust"](https://astral.sh/blog/uv)
- Migrate Dependency Management from
piptoPoetry#321 - Original Poetry migration request. This approach provides the same benefits (better dependency management, virtual environment handling, dependency groups) plus performance and no lock-in. The key insight from Migrate Dependency Management frompiptoPoetry#321's discussion is to use PEP 735 instead of tool-specific formats.
Note on approach: This implementation differs from a pure uv migration by using PEP 735 dependency groups instead of uv-specific configuration. This gives us uv's performance benefits today while keeping our configuration standards-based and tool-agnostic for the future. When pip adds PEP 735 support, we won't need to change our pyproject.toml at all.