Skip to content

Commit 8c8d903

Browse files
nanotaboadaCopilot
andcommitted
chore(deps): migrate to uv with PEP 735 dependency groups (#447)
- Add [project], [project.dependencies], and [dependency-groups] (test, lint, dev) to pyproject.toml following PEP 735 standard - Replace requirements.txt, requirements-lint.txt, requirements-test.txt with pyproject.toml as the single source of truth - Generate uv.lock for fully pinned, reproducible dependency resolution - Update GitHub Actions (python-ci.yml, python-cd.yml) to install and run commands via uv instead of pip - Update Dockerfile: builder stage uses uv export piped to pip wheel for offline reproducible builds; runtime stage installs from wheelhouse with no network access - Update README.md and copilot-instructions.md with uv setup and usage Co-authored-by: GitHub Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 805b08c commit 8c8d903

File tree

12 files changed

+1519
-63
lines changed

12 files changed

+1519
-63
lines changed

.github/copilot-instructions.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,34 @@ tests/ — pytest integration tests
5454
### Quick Start
5555

5656
```bash
57-
pip install -r requirements.txt
58-
pip install -r requirements-test.txt
59-
pip install -r requirements-lint.txt
60-
uvicorn main:app --reload --port 9000 # http://localhost:9000/docs
61-
pytest # run tests
62-
pytest --cov=./ --cov-report=term # with coverage (target >=80%)
63-
flake8 .
64-
black --check .
57+
# Setup (using uv)
58+
uv venv
59+
source .venv/bin/activate # Linux/macOS; use .venv\Scripts\activate on Windows
60+
uv pip install --group dev
61+
62+
# Run application
63+
uv run uvicorn main:app --reload --port 9000 # http://localhost:9000/docs
64+
65+
# Run tests
66+
uv run pytest # run tests
67+
uv run pytest --cov=./ --cov-report=term # with coverage (target >=80%)
68+
69+
# Linting and formatting
70+
uv run flake8 .
71+
uv run black --check .
72+
73+
# Docker
6574
docker compose up
6675
docker compose down -v
6776
```
6877

6978
### Pre-commit Checks
7079

7180
1. Update `CHANGELOG.md` `[Unreleased]` section (Added / Changed / Fixed / Removed)
72-
2. `flake8 .` — must pass
73-
3. `black --check .` — must pass
74-
4. `pytest` — all tests must pass
75-
5. `pytest --cov=./ --cov-report=term` — coverage must be >=80%
81+
2. `uv run flake8 .` — must pass
82+
3. `uv run black --check .` — must pass
83+
4. `uv run pytest` — all tests must pass
84+
5. `uv run pytest --cov=./ --cov-report=term` — coverage must be >=80%
7685
6. Commit message follows Conventional Commits format (enforced by commitlint)
7786

7887
### Commits
@@ -95,7 +104,7 @@ Example: `feat(api): add player stats endpoint (#42)`
95104
### Ask before changing
96105

97106
- Database schema (`schemas/player_schema.py` — no Alembic, use tools/ seed scripts manually)
98-
- Dependencies (`requirements*.txt`)
107+
- Dependencies (`pyproject.toml` with PEP 735 dependency groups)
99108
- CI/CD configuration (`.github/workflows/`)
100109
- Docker setup
101110
- API contracts (breaking Pydantic model changes)

.github/workflows/python-cd.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ name: Python CD
66
on:
77
push:
88
tags:
9-
- 'v*.*.*-*'
9+
- "v*.*.*-*"
1010

1111
env:
12-
PYTHON_VERSION_FILE: '.python-version'
12+
PYTHON_VERSION_FILE: ".python-version"
1313
PACKAGE_NAME: nanotaboada/python-samples-fastapi-restful
1414

1515
jobs:
@@ -69,20 +69,25 @@ jobs:
6969
uses: actions/setup-python@v6.2.0
7070
with:
7171
python-version-file: ${{ env.PYTHON_VERSION_FILE }}
72-
cache: 'pip'
72+
73+
- name: Set up uv
74+
uses: astral-sh/setup-uv@v4
75+
with:
76+
version: "latest"
7377

7478
- name: Install test dependencies
7579
run: |
76-
python -m pip install --upgrade pip
77-
pip install -r requirements-test.txt
80+
uv venv
81+
uv pip install --group test
82+
uv pip install --group dev
7883
7984
- name: Run tests with pytest
8085
run: |
81-
pytest -v
86+
uv run pytest -v
8287
8388
- name: Generate coverage report
8489
run: |
85-
pytest --cov=./ --cov-report=xml --cov-report=term
90+
uv run pytest --cov=./ --cov-report=xml --cov-report=term
8691
8792
- name: Log in to GitHub Container Registry
8893
uses: docker/login-action@v3.7.0

.github/workflows/python-ci.yml

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ name: Python CI
55

66
on:
77
push:
8-
branches: [ master ]
8+
branches: [master]
99
pull_request:
10-
branches: [ master ]
10+
branches: [master]
1111

1212
jobs:
1313
lint:
@@ -21,24 +21,28 @@ jobs:
2121
- name: Lint commit messages
2222
uses: wagoid/commitlint-github-action@v6.2.1
2323

24+
- name: Set up uv
25+
uses: astral-sh/setup-uv@v4
26+
with:
27+
version: "latest"
28+
2429
- name: Set up Python
2530
uses: actions/setup-python@v6.2.0
2631
with:
27-
python-version-file: '.python-version'
28-
cache: 'pip'
32+
python-version-file: ".python-version"
2933

3034
- name: Install lint dependencies
3135
run: |
32-
python -m pip install --upgrade pip
33-
pip install -r requirements-lint.txt
36+
uv venv
37+
uv pip install --group lint
3438
3539
- name: Lint with Flake8
3640
run: |
37-
flake8 .
41+
uv run flake8 .
3842
3943
- name: Check code formatting with Black
4044
run: |
41-
black --check .
45+
uv run black --check .
4246
4347
test:
4448
needs: lint
@@ -49,24 +53,29 @@ jobs:
4953
- name: Checkout repository
5054
uses: actions/checkout@v6.0.2
5155

56+
- name: Set up uv
57+
uses: astral-sh/setup-uv@v4
58+
with:
59+
version: "latest"
60+
5261
- name: Set up Python
5362
uses: actions/setup-python@v6.2.0
5463
with:
55-
python-version-file: '.python-version'
56-
cache: 'pip'
64+
python-version-file: ".python-version"
5765

5866
- name: Install test dependencies
5967
run: |
60-
python -m pip install --upgrade pip
61-
pip install -r requirements-test.txt
68+
uv venv
69+
uv pip install --group test
70+
uv pip install --group dev
6271
6372
- name: Run tests with pytest
6473
run: |
65-
pytest -v
74+
uv run pytest -v
6675
6776
- name: Generate coverage report
6877
run: |
69-
pytest --cov=./ --cov-report=xml --cov-report=term
78+
uv run pytest --cov=./ --cov-report=xml --cov-report=term
7079
7180
- name: Upload coverage report artifact
7281
uses: actions/upload-artifact@v7.0.0

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ ipython_config.py
113113
# https://pdm.fming.dev/#use-with-ide
114114
.pdm.toml
115115

116+
# uv
117+
# uv uses a lock file for reproducible builds. Include it in version control.
118+
# No need to ignore uv.lock - it should be committed.
119+
116120
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
117121
__pypackages__/
118122

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,17 @@ This project uses famous football coaches as release codenames, following an A-Z
6666
- `logger.error(f"...")` replaced with `logger.exception("...: %s", error)` in all `SQLAlchemyError` handlers (#66)
6767
- EN dashes replaced with ASCII hyphens in `seed_002` log and argparse strings (#66)
6868
- `logger.error` replaced with `logger.exception` in `sqlite3.Error` handlers in `seed_001` and `seed_002` (#66)
69+
- `pyproject.toml` migrated to full PEP 735 format: `[project]`, `[project.dependencies]`, and `[dependency-groups]` (`test`, `lint`, `dev`) (#447)
70+
- GitHub Actions CI/CD (`python-ci.yml`, `python-cd.yml`) updated to install and run via `uv` instead of `pip` (#447)
71+
- Dockerfile updated: builder stage uses `uv export | pip wheel` for reproducible offline wheel builds; runtime installs from pre-built wheels with no network access (#447)
72+
- `uv.lock` added for fully pinned, reproducible dependency resolution across all environments (#447)
6973

7074
### Deprecated
7175

7276
### Removed
7377

7478
- `postman_collections/` directory replaced by `rest/players.rest` (#493)
79+
- `requirements.txt`, `requirements-lint.txt`, `requirements-test.txt` replaced by `pyproject.toml` with PEP 735 dependency groups (#447)
7580

7681
### Fixed
7782

Dockerfile

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
# ------------------------------------------------------------------------------
22
# Stage 1: Builder
3-
# This stage builds the application and its dependencies.
3+
# This stage resolves and pre-builds all dependency wheels for offline installation.
4+
# No application source code is copied here — only pyproject.toml and uv.lock.
45
# ------------------------------------------------------------------------------
56
# Python version should match .python-version file (currently 3.13.3)
67
FROM python:3.13.3-slim-bookworm AS builder
78

89
WORKDIR /app
910

10-
# Install system build tools for packages with native extensions
11+
# Install system build tools required to compile native extensions (e.g. gevent, greenlet)
1112
RUN apt-get update && \
1213
apt-get install -y --no-install-recommends build-essential gcc libffi-dev libssl-dev && \
1314
rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
1415

15-
# Build all dependencies into wheels for reproducibility and speed
16-
COPY --chown=root:root --chmod=644 requirements.txt .
17-
RUN pip wheel --no-cache-dir --wheel-dir=/app/wheelhouse -r requirements.txt
16+
# Resolve and build all dependency wheels from pyproject.toml and uv.lock:
17+
# uv export reads uv.lock to produce a pinned, reproducible dependency list;
18+
# pip wheel compiles each resolved package into a .whl file for offline installation
19+
COPY --chown=root:root --chmod=644 pyproject.toml uv.lock ./
20+
RUN pip install uv --quiet && \
21+
uv export --no-hashes | pip wheel --no-cache-dir --wheel-dir=/app/wheelhouse -r /dev/stdin
1822

1923
# ------------------------------------------------------------------------------
2024
# Stage 2: Runtime
@@ -42,9 +46,8 @@ COPY assets/ ./assets/
4246
# Copy pre-built wheels from builder
4347
COPY --from=builder /app/wheelhouse/ /app/wheelhouse/
4448

45-
# Install dependencies
46-
COPY requirements.txt .
47-
RUN pip install --no-cache-dir --no-index --find-links /app/wheelhouse -r requirements.txt && \
49+
# Install all pre-built wheels from the builder stage; no network access required
50+
RUN pip install --no-cache-dir --no-index --find-links /app/wheelhouse /app/wheelhouse/*.whl && \
4851
rm -rf /app/wheelhouse
4952

5053
# Copy application source code
@@ -55,8 +58,6 @@ COPY routes/ ./routes/
5558
COPY schemas/ ./schemas/
5659
COPY services/ ./services/
5760

58-
# https://rules.sonarsource.com/docker/RSPEC-6504/
59-
6061
# Copy entrypoint and healthcheck scripts
6162
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
6263
COPY --chmod=755 scripts/healthcheck.sh ./healthcheck.sh
@@ -66,6 +67,7 @@ COPY --chmod=755 scripts/healthcheck.sh ./healthcheck.sh
6667
COPY --chmod=755 storage/ ./hold/
6768

6869
# Add non-root user and make volume mount point writable
70+
# Avoids running the container as root (see: https://rules.sonarsource.com/docker/RSPEC-6504/)
6971
RUN adduser --system --disabled-password --group fastapi && \
7072
mkdir -p /storage && \
7173
chown fastapi:fastapi /storage

README.md

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,63 @@ This project uses `.python-version` to specify the required Python version. If y
3636

3737
Alternatively, ensure you have Python 3.13.3 (or the version specified in `.python-version`) installed.
3838

39+
## Setup
40+
41+
### Prerequisites
42+
43+
- Python 3.13+
44+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip
45+
46+
### Installation
47+
48+
1. Install uv (if you haven't already):
49+
50+
```bash
51+
curl -LsSf https://astral.sh/uv/install.sh | sh
52+
```
53+
54+
2. Create a virtual environment and install dependencies:
55+
56+
```bash
57+
uv venv
58+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
59+
uv pip install --group dev
60+
```
61+
62+
**Alternative (using pip)**:
63+
64+
```bash
65+
python -m venv .venv
66+
source .venv/bin/activate # On Windows: .venv\Scripts\activate
67+
pip install -r requirements.txt
68+
pip install -r requirements-lint.txt
69+
pip install -r requirements-test.txt
70+
```
71+
3972
## Install
4073

41-
```console
42-
pip install -r requirements.txt
43-
pip install -r requirements-lint.txt
44-
pip install -r requirements-test.txt
74+
Dependencies are defined in `pyproject.toml` using PEP 735 standards. Install them with:
75+
76+
```bash
77+
uv pip install --group dev
4578
```
4679

80+
Or with specific groups:
81+
82+
- `uv pip install` - Install production dependencies only
83+
- `uv pip install --group test` - Install test dependencies
84+
- `uv pip install --group lint` - Install linting dependencies
85+
- `uv pip install --group dev` - Install all (test + lint + production)
86+
4787
## Start
4888

49-
```console
89+
```bash
90+
uv run uvicorn main:app --reload --port 9000
91+
```
92+
93+
Or using pip:
94+
95+
```bash
5096
uvicorn main:app --reload --port 9000
5197
```
5298

@@ -62,11 +108,38 @@ http://localhost:9000/docs
62108

63109
The [`rest/players.rest`](rest/players.rest) file covers all CRUD operations and can be run directly in VS Code with the [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client) extension.
64110

111+
## Running Tests
112+
113+
```bash
114+
uv run pytest -v
115+
```
116+
117+
## Code Quality
118+
119+
### Linting
120+
121+
```bash
122+
uv run flake8 .
123+
```
124+
125+
### Code Formatting
126+
127+
```bash
128+
uv run black --check .
129+
uv run black . # Auto-format
130+
```
131+
132+
### Coverage
133+
134+
```bash
135+
uv run pytest --cov=./ --cov-report=term
136+
```
137+
65138
## Container
66139

67140
### Docker Compose
68141

69-
This setup uses [Docker Compose](https://docs.docker.com/compose/) to build and run the app and manage a persistent SQLite database stored in a Docker volume.
142+
This setup uses [Docker Compose](https://docs.docker.com/compose/) to build and run the app and manage a persistent SQLite database stored in a Docker volume. The Dockerfile uses PEP 735 dependency groups defined in `pyproject.toml`.
70143

71144
#### Build the image
72145

0 commit comments

Comments
 (0)