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
64 changes: 42 additions & 22 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -76,12 +85,12 @@ uv add --group dev <package>
## 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 .

Expand All @@ -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

Expand Down
185 changes: 91 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/<your-package>/` 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/<your-package>/main.py` with your own

# Format
uv run ruff format .
3. **Write tests** in `tests/test_<module>.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 <pkg>`.
3. **Write your code** in `src/<package>/`.
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

Expand Down
Loading