diff --git a/CLAUDE.md b/CLAUDE.md index 2ac4c8f..628fdd3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,16 +5,23 @@ effectively in this repository. ## Project Overview -A base template for Python applications. Clone this repo and replace -`app/` with your own code. The template ships with: +A base template for Python applications. The primary workflow is **dev +container first** — developers open this in VS Code, click "Reopen in +Container", and have a fully working environment with no local Python setup. +Clone this repo and replace `src/app/` with your own code. + +The template ships with: - **Python 3.13** managed by [uv](https://docs.astral.sh/uv/) -- **Docker** — multi-stage build (`dev` for development, `prod` for deployment) -- **Dev Container** — VS Code dev container using the `dev` Docker target -- **Ruff** — linting and formatting (replaces flake8, isort, black) -- **Mypy** — strict static type checking -- **Pytest** — test runner with coverage -- **pip-audit** — dependency vulnerability scanning (CI); **Bandit** available locally +- **Docker** — multi-stage build (`dev` for dev containers, `prod` for deployment) +- **Dev Container** — VS Code dev container using the `dev` Docker target; runs + as non-root `devuser`, `.venv` is kept in a named volume separate from the + workspace mount +- **Ruff** — linting and formatting (replaces flake8, isort, black); runs on save +- **Mypy** — strict static type checking; VS Code defers to the mypy extension +- **Pytest** — test runner; coverage flags are explicit, not in addopts +- **pip-audit** — dependency vulnerability scanning in CI +- **Bandit** — available locally for static security scanning - **GitHub Actions** — CI (lint, test, pip-audit, Docker build) + CodeQL for SAST ## Repository Layout @@ -38,6 +45,8 @@ A base template for Python applications. Clone this repo and replace ## Common Commands +All commands run inside the dev container terminal or locally with uv installed. + ```bash # Install all dependencies (creates .venv) uv sync --all-groups @@ -60,7 +69,7 @@ uv run ruff format . # Type check uv run mypy src/ -# Security scan (static) +# Security scan (static) — available locally, not run in CI (CodeQL covers SAST) uv run bandit -r src/ -c pyproject.toml # Dependency vulnerability scan @@ -76,12 +85,12 @@ uv add --group dev ## Docker Commands ```bash -# Build and run production container -docker compose up app - -# Run tests inside dev container +# Run tests in Docker (no local Python needed) docker compose run --rm test +# Build and run the production container +docker compose up app + # Build production image directly docker build --target prod -t myapp:latest . @@ -92,18 +101,29 @@ docker build --target dev -t myapp:dev . ## Dev Container Open this repository in VS Code and select **"Reopen in Container"**. The -devcontainer builds from the `dev` Dockerfile target, installs all -dependencies, and configures extensions automatically. +devcontainer builds from the `dev` Dockerfile target. On first open: + +1. Docker builds the `dev` image (~1–2 min) +2. `postCreateCommand` runs `uv sync --all-groups` to populate `.venv` +3. All VS Code extensions install automatically (Ruff, Mypy, pytest, Docker) + +The `.venv` lives in a named Docker volume (not the workspace mount) so it +persists across container rebuilds and doesn't appear in the host file system. ## Architecture Notes -- Source code lives under `src/app/` (src-layout) to prevent import ambiguity - between installed packages and local source. -- `uv.lock` should be committed — it ensures reproducible installs across - environments and in CI. -- The production Docker image copies only `.venv` and `src/` from the builder - stage, keeping the final image small and without build tooling. -- Non-root user (`appuser`) is used in the production container; `devuser` in the dev container. +- Source code lives under `src/app/` (src-layout) to prevent import ambiguity. + `pythonpath = ["src"]` in pytest config means tests work without an editable + install, but `uv sync` installs the project in editable mode regardless. +- `uv.lock` must be committed — it ensures reproducible installs across the dev + container, CI, and any other environment. +- The builder stage uses a two-step sync: `--no-install-project` first (external + deps, well-cached layer), then full `uv sync --no-dev` with source files + present (hatchling needs README.md and LICENSE to build the wheel). +- The production image copies only `.venv` and `src/` from the builder stage, + running as non-root `appuser`. +- CI drops bandit (CodeQL covers the same SAST categories with data-flow + analysis). pip-audit handles dependency CVEs. ## Testing Conventions diff --git a/README.md b/README.md index fccd8c4..3cd0643 100644 --- a/README.md +++ b/README.md @@ -1,133 +1,130 @@ -# Python-Project-Base +# Python Project Base -A production-ready base template for Python projects. Includes Docker, dev -container, uv package management, linting, type checking, testing, and -GitHub Actions CI with security scanning. +A ready-to-code Python template. Open in VS Code, click **Reopen in Container**, and you have a fully configured Python environment — no local Python installation required. -## Stack +Everything is pre-wired: testing, linting, type checking, debugging, and CI that runs on every pull request. -| Tool | Purpose | -|------|---------| -| [Python 3.13](https://docs.python.org/3.13/) | Language runtime | -| [uv](https://docs.astral.sh/uv/) | Package manager & virtual envs | -| [Ruff](https://docs.astral.sh/ruff/) | Linting & formatting | -| [Mypy](https://mypy.readthedocs.io/) | Static type checking | -| [Pytest](https://docs.pytest.org/) | Testing | -| [Bandit](https://bandit.readthedocs.io/) | Security static analysis | -| [pip-audit](https://pypi.org/project/pip-audit/) | Dependency vulnerability scanning | -| [Docker](https://docs.docker.com/) | Multi-stage container builds | +## Prerequisites -## Quick Start +- [VS Code](https://code.visualstudio.com/) +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) +- [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) -### Local Development (with uv) +That's it. No Python, no pip, no virtual environments to manage yourself. -```bash -# Clone and set up -git clone https://github.com/samhodgkinson/Python-Project-Base.git -cd Python-Project-Base +## Getting started + +1. Click **Use this template** → **Create a new repository** on GitHub +2. Clone your new repository and open the folder in VS Code +3. VS Code will prompt **"Reopen in Container"** — click it + - (Or: Command Palette → `Dev Containers: Reopen in Container`) +4. The container builds on first open (~1–2 min). Dependencies install automatically. +5. Open a terminal inside VS Code and run the tests to confirm everything works: -# Install uv (if not already installed) -curl -LsSf https://astral.sh/uv/install.sh | sh +```bash +uv run pytest +``` -# Install all dependencies and create .venv -uv sync --all-groups +You should see all tests pass. The environment is ready. -# Run the app -uv run python -m app.main +## Running tests +```bash # Run tests uv run pytest + +# Run tests with a coverage report +uv run pytest --cov=src --cov-report=term-missing ``` -### VS Code Dev Container +The VS Code Testing panel (flask icon in the sidebar) also discovers and runs tests with a click. + +## Writing code -1. Install the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) -2. Open the repository in VS Code -3. Click **"Reopen in Container"** when prompted (or via Command Palette) +Put your code in `src/app/` and tests in `tests/`. The editor is pre-configured to: -The container builds from the `dev` Dockerfile target with all tools and -VS Code extensions pre-configured. +- **Format on save** — Ruff fixes style automatically when you save a file +- **Highlight type errors** — Mypy flags type problems as you type +- **Run tests** — from the Testing panel or the terminal +- **Debug** — F5 launches the debugger (see the pre-configured launch profiles in the Run panel) -### Docker +## Adding dependencies + +Inside the container terminal: ```bash -# Run tests in the dev container -docker compose run --rm test +# Add a runtime dependency +uv add requests -# Run the production app -docker compose up app +# Add a dev-only dependency (testing tools, linters, etc.) +uv add --group dev pytest-asyncio -# Build production image only -docker build --target prod -t myapp:latest . +# Always commit the lock file after changing dependencies +git add uv.lock && git commit -m "Add requests" ``` -## Project Structure +The `uv.lock` file pins every dependency to an exact version so CI and other developers get identical environments. -``` -. -├── src/ -│ └── app/ -│ ├── __init__.py -│ └── main.py # Entry point -├── tests/ -│ └── test_main.py -├── .devcontainer/ -│ └── devcontainer.json # VS Code dev container -├── .github/ -│ └── workflows/ -│ ├── ci.yml # Lint, test, security, Docker build -│ └── codeql.yml # CodeQL security analysis -├── .vscode/ -│ ├── settings.json # Editor settings -│ ├── extensions.json # Recommended extensions -│ └── launch.json # Debug configurations -├── Dockerfile # Multi-stage: dev / prod -├── docker-compose.yml -└── pyproject.toml # All config: uv, ruff, mypy, pytest, bandit -``` +## Using this as a base for a new project -## Development Workflow +1. **Rename the package** — move `src/app/` to `src//` and update `pyproject.toml`: -```bash -# Add a dependency -uv add requests + ```toml + [project] + name = "your-project" -# Add a dev dependency -uv add --group dev pytest-asyncio + [tool.hatch.build.targets.wheel] + packages = ["src/your-package"] + ``` -# Lint -uv run ruff check . +2. **Replace the example code** in `src//main.py` with your own -# Format -uv run ruff format . +3. **Write tests** in `tests/test_.py` mirroring your source layout -# Type check -uv run mypy src/ +4. **Push** — CI runs automatically on every pull request -# Security scan -uv run bandit -r src/ -c pyproject.toml -uv run pip-audit -``` +## CI — what runs on every pull request + +| Check | What it does | +|-------|-------------| +| Lint & Format | Ruff checks code style | +| Type check | Mypy strict mode | +| Tests | pytest with coverage | +| Security | pip-audit scans dependencies for known vulnerabilities | +| Docker build | Builds both the dev and production images | +| CodeQL | GitHub's security analysis (also runs weekly) | + +Set up **branch protection rules** on `main` (GitHub → Settings → Branches) to require all checks to pass before a PR can be merged. + +## Production Docker image -Always commit `uv.lock` when dependencies change — it ensures reproducible -installs in CI and other environments. +The `Dockerfile` has two targets: -## GitHub Actions +- **`dev`** — used by the dev container and `docker compose run --rm test` +- **`prod`** — a minimal image with only runtime dependencies, running as a non-root user -| Workflow | Triggers | Jobs | -|----------|----------|------| -| **CI** | push/PR to `main` | Lint, Test (with coverage), Security scan, Docker build | -| **CodeQL** | push/PR to `main`, weekly | Python security analysis | +```bash +# Run the test suite in Docker (no local setup needed) +docker compose run --rm test -## Using This as a Template +# Build and run the production image +docker build --target prod -t myapp:latest . +docker run myapp:latest +``` -1. **Rename the package**: update `src/app/` to your package name and update - `pyproject.toml` (`name`, `[tool.hatch.build.targets.wheel]`). -2. **Update dependencies**: add your runtime deps with `uv add `. -3. **Write your code** in `src//`. -4. **Write tests** in `tests/`. -5. **Commit `uv.lock`**: run `uv lock` and commit the lock file. -6. **Push** — CI will run automatically. +## Project layout + +``` +src/app/ ← application source code (rename app/ to your package name) +tests/ ← tests (mirror src/ structure) +.devcontainer/ ← VS Code dev container config +.github/workflows/ ← CI pipelines (ci.yml, codeql.yml) +.vscode/ ← editor settings, extensions, debug configs +Dockerfile ← dev and production images +docker-compose.yml ← test and app services +pyproject.toml ← all project config (uv, ruff, mypy, pytest) +uv.lock ← locked dependencies — always commit this +``` ## License