Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,34 @@ tests/ — pytest integration tests
### Quick Start

```bash
pip install -r requirements.txt
pip install -r requirements-test.txt
pip install -r requirements-lint.txt
uvicorn main:app --reload --port 9000 # http://localhost:9000/docs
pytest # run tests
pytest --cov=./ --cov-report=term # with coverage (target >=80%)
flake8 .
black --check .
# Setup (using uv)
uv venv
source .venv/bin/activate # Linux/macOS; use .venv\Scripts\activate on Windows
uv pip install --group dev

# Run application
uv run uvicorn main:app --reload --port 9000 # http://localhost:9000/docs

# Run tests
uv run pytest # run tests
uv run pytest --cov=./ --cov-report=term # with coverage (target >=80%)

# Linting and formatting
uv run flake8 .
uv run black --check .

# Docker
docker compose up
docker compose down -v
```

### Pre-commit Checks

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

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

- Database schema (`schemas/player_schema.py` — no Alembic, use tools/ seed scripts manually)
- Dependencies (`requirements*.txt`)
- Dependencies (`pyproject.toml` with PEP 735 dependency groups)
- CI/CD configuration (`.github/workflows/`)
- Docker setup
- API contracts (breaking Pydantic model changes)
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/python-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ name: Python CD
on:
push:
tags:
- 'v*.*.*-*'
- "v*.*.*-*"

env:
PYTHON_VERSION_FILE: '.python-version'
PYTHON_VERSION_FILE: ".python-version"
PACKAGE_NAME: nanotaboada/python-samples-fastapi-restful

jobs:
Expand Down Expand Up @@ -69,20 +69,20 @@ jobs:
uses: actions/setup-python@v6.2.0
with:
python-version-file: ${{ env.PYTHON_VERSION_FILE }}
cache: 'pip'

- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Install test dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-test.txt
uv venv
uv pip install --group dev

- name: Run tests with pytest
run: |
pytest -v

- name: Generate coverage report
run: |
pytest --cov=./ --cov-report=xml --cov-report=term
uv run pytest --cov=./ --cov-report=xml --cov-report=term -v

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3.7.0
Expand Down
36 changes: 22 additions & 14 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ name: Python CI

on:
push:
branches: [ master ]
branches: [master]
pull_request:
branches: [ master ]
branches: [master]

jobs:
lint:
Expand All @@ -21,24 +21,28 @@ jobs:
- name: Lint commit messages
uses: wagoid/commitlint-github-action@v6.2.1

- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python
uses: actions/setup-python@v6.2.0
with:
python-version-file: '.python-version'
cache: 'pip'
python-version-file: ".python-version"

- name: Install lint dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-lint.txt
uv venv
uv pip install --group lint

- name: Lint with Flake8
run: |
flake8 .
uv run flake8 .

- name: Check code formatting with Black
run: |
black --check .
uv run black --check .

test:
needs: lint
Expand All @@ -49,24 +53,28 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v6.0.2

- name: Set up uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Set up Python
uses: actions/setup-python@v6.2.0
with:
python-version-file: '.python-version'
cache: 'pip'
python-version-file: ".python-version"

- name: Install test dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-test.txt
uv venv
uv pip install --group dev

- name: Run tests with pytest
run: |
pytest -v
uv run pytest -v

- name: Generate coverage report
run: |
pytest --cov=./ --cov-report=xml --cov-report=term
uv run pytest --cov=./ --cov-report=xml --cov-report=term

- name: Upload coverage report artifact
uses: actions/upload-artifact@v7.0.0
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ ipython_config.py
# https://pdm.fming.dev/#use-with-ide
.pdm.toml

# uv
# uv uses a lock file for reproducible builds. Include it in version control.
# No need to ignore uv.lock - it should be committed.

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,17 @@ This project uses famous football coaches as release codenames, following an A-Z
- `logger.error(f"...")` replaced with `logger.exception("...: %s", error)` in all `SQLAlchemyError` handlers (#66)
- EN dashes replaced with ASCII hyphens in `seed_002` log and argparse strings (#66)
- `logger.error` replaced with `logger.exception` in `sqlite3.Error` handlers in `seed_001` and `seed_002` (#66)
- `pyproject.toml` migrated to full PEP 735 format: `[project]` (with `dependencies` field) and `[dependency-groups]` (`test`, `lint`, `dev`) (#447)
- GitHub Actions CI/CD (`python-ci.yml`, `python-cd.yml`) updated to install and run via `uv` instead of `pip` (#447)
- 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)
- `uv.lock` added for fully pinned, reproducible dependency resolution across all environments (#447)

### Deprecated

### Removed

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

### Fixed

Expand Down
23 changes: 13 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
# ------------------------------------------------------------------------------
# Stage 1: Builder
# This stage builds the application and its dependencies.
# This stage resolves and pre-builds all dependency wheels for offline installation.
# No application source code is copied here — only pyproject.toml and uv.lock.
# ------------------------------------------------------------------------------
# Python version should match .python-version file (currently 3.13.3)
FROM python:3.13.3-slim-bookworm AS builder

WORKDIR /app

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

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

# ------------------------------------------------------------------------------
# Stage 2: Runtime
Expand Down Expand Up @@ -42,9 +46,8 @@ COPY assets/ ./assets/
# Copy pre-built wheels from builder
COPY --from=builder /app/wheelhouse/ /app/wheelhouse/

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

# Copy application source code
Expand All @@ -54,8 +57,7 @@ COPY models/ ./models/
COPY routes/ ./routes/
COPY schemas/ ./schemas/
COPY services/ ./services/

# https://rules.sonarsource.com/docker/RSPEC-6504/
COPY tools/ ./tools/

# Copy entrypoint and healthcheck scripts
COPY --chmod=755 scripts/entrypoint.sh ./entrypoint.sh
Expand All @@ -66,6 +68,7 @@ COPY --chmod=755 scripts/healthcheck.sh ./healthcheck.sh
COPY --chmod=755 storage/ ./hold/

# Add non-root user and make volume mount point writable
# Avoids running the container as root (see: https://rules.sonarsource.com/docker/RSPEC-6504/)
RUN adduser --system --disabled-password --group fastapi && \
mkdir -p /storage && \
chown fastapi:fastapi /storage
Expand Down
75 changes: 69 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,53 @@ This project uses `.python-version` to specify the required Python version. If y

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

## Setup

### Prerequisites

- Python 3.13+
- [uv](https://docs.astral.sh/uv/) (recommended) or pip

### Installation

1. Install uv (if you haven't already):

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

2. Create a virtual environment and install dependencies:

```bash
uv venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
uv pip install --group dev
```

## Install

```console
pip install -r requirements.txt
pip install -r requirements-lint.txt
pip install -r requirements-test.txt
Dependencies are defined in `pyproject.toml` using PEP 735 standards. Install them with:

```bash
uv pip install --group dev
```

Or with specific groups:

- `uv pip install` - Install production dependencies only
- `uv pip install --group test` - Install test dependencies
- `uv pip install --group lint` - Install linting dependencies
- `uv pip install --group dev` - Install all (test + lint + production)

## Start

```console
```bash
uv run uvicorn main:app --reload --port 9000
```

Or using pip:

```bash
uvicorn main:app --reload --port 9000
```

Expand All @@ -62,11 +98,38 @@ http://localhost:9000/docs

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.

## Running Tests

```bash
uv run pytest -v
```

## Code Quality

### Linting

```bash
uv run flake8 .
```

### Code Formatting

```bash
uv run black --check .
uv run black . # Auto-format
```

### Coverage

```bash
uv run pytest --cov=./ --cov-report=term
```

## Container

### Docker Compose

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.
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`.

#### Build the image

Expand Down
Loading