A personal collection of Copier templates for scaffolding Python projects — one template per project shape, each pre-wired with the same tool stack and conventions so a new project starts with linting, type checking, complexity limits, spell checking, secret scanning, and git hooks already working, not something to bolt on later.
Inspired by (and started from) Nacho Lorca's
mold, from his EuroPython 2026 talk.
Each shape is an independent Copier template in its own subdirectory — there's no single root template with a "what kind of project?" prompt, because the shapes genuinely diverge in structure (some aren't even src-layout). Pick the one that matches what you're building:
| Shape | Directory | For |
|---|---|---|
| Library/Package | library/ |
Anything meant to be imported elsewhere — published to PyPI or installed by other projects. |
| Application (CLI/GUI) | app/ |
A runtime entry point that isn't a library — CLI tools or GUI apps (tkinter, pygame, etc.). Toolkit-agnostic; README documents Typer for CLI. |
| Web Application | web-app/ |
A running server — FastAPI, Flask, or Dash. Framework-agnostic; README documents all three. |
| Data Analysis/Research | data-analysis/ |
Notebook-driven exploration: data/, notebooks/, reports/figures/, plus a src/ package for anything promoted out of a notebook. Prompts for a notebook runtime (VS Code/ipykernel, JupyterLab, both, or none). |
| Multi-subpackage Project | multi-subpackage/ |
One package, N independent self-contained units sharing a lockfile — a production pipeline (extract/transform/load per unit) or a book/tutorial study repo (one unit per chapter) are both this shape. |
| Single-file Script/Collection | scripts/ |
Flat, not src-layout — standalone scripts (e.g. daily coding challenges) sharing one shared/ helpers module. |
A seventh shape, a uv workspace monorepo, is deliberately not
templated here — see python-project-structures.md in this repo for why,
and for the fuller reasoning behind each shape above.
Install these once, system-wide, before using any template:
uv— the Python package/environment manager everything else here depends on, includingcopieritself and every generated project'suv sync/uv run.curl -LsSf https://astral.sh/uv/install.sh | shcopier— renders the templates.uv tool install copier
just— the command runner every generated project'sjustfilerelies on (just install,just check, …). Installable viauvtoo, as therust-justPyPI package (same binary, published under that name sincejustwas taken):uv tool install rust-just
gitleaks— the secret-scanning binary each generated project pins (inprek.toml) and checks against. It's a standalone Go binary, not somethinguv syncinstalls:GITLEAKS_VERSION=<version> # match what you'll pin at generation time curl -sSL -o gitleaks.tar.gz "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" tar xzf gitleaks.tar.gz gitleaks sudo mv gitleaks /usr/local/bin/ rm gitleaks.tar.gz
prek— no separate install needed. It's a dev dependency of every generated project, sojust install(uv sync) pulls it into that project's own venv automatically.
copier copy gh:karelplanken/python-project-templates/library ./my-new-lib --trustSwap library for any shape directory from the table above. --trust is
required — the templates use Jinja features (like the to_nice_yaml
filter for the answers file) that Copier treats as needing explicit trust,
which is fine for a template you wrote yourself.
Copier will prompt for project_name, project_slug, a description, your
Python version, author name/email, the gitleaks version to pin, and a
copyright year — then generate the project. From there:
cd my-new-lib
just install # uv sync — pulls in ruff, mypy, complexipy, codespell, pytest, prek
git init # optional — install-hooks runs it if this isn't a repo yet
just install-hooks # one-time: wires up prek's git hooks
just check # format + types + complexity + spelling + version-sync + testsEach generated project's own README.md documents its specific dev
commands, git hook behavior, and (for app//web-app//scripts/) the
toolkit/framework choices left for you to make after generation.
Every shape uses the same stack: uv for
environments and dependencies, Ruff for
linting and formatting, mypy --strict for type checking,
complexipy for cognitive
complexity limits, pytest + coverage, codespell
for spell checking, gitleaks for
secret scanning, and prek (a Rust
pre-commit reimplementation) for git hooks — all driven through one
justfile per project, so the commands you run locally are exactly what
the git hooks run too.
One recurring design decision worth knowing: a tool's version only needs
active drift-checking (just check-versions-sync) when it's pinned in two
independently-edited places. Everything installed into the project's own
venv (ruff, mypy, complexipy, codespell, pytest, prek itself)
has exactly one pin — pyproject.toml — so uv sync keeps it correct by
construction. gitleaks is the one exception: a standalone Go binary
outside the venv, pinned separately in prek.toml and each README's
install snippet, which is why it's the only tool check-versions-sync
actually checks.
The per-shape templates duplicate their shared tooling files (justfile,
prek.toml.jinja, the [tool.*] blocks in pyproject.toml.jinja) rather
than composing them from one source — Copier has no native mechanism to
share config across independent templates. check_shared_drift.py
(just check-shared-drift) catches unintended drift: it diffs each
shape's shared recipes/hooks/config blocks against library/, the
reference shape, tolerating shape-specific additions (like
data-analysis's extra clean-notebooks recipe) but flagging anything
library has that another shape is missing or disagrees with.
MIT.