Copier template for modern Python packages. This repository is the template engine and its source material, not one of the packages generated by it.
Copier reads the answers and rendering rules in copier.yaml, evaluates the
files under template/, and writes a new project into a destination directory.
The generated project is independent: it gets its own pyproject.toml, source
tree, tests, documentation, CI configuration, and Git history.
The main flow is:
copier.yaml + template/ + extensions/
|
| answers and Jinja rendering
v
generated Python project
The generated project also receives .copier-answers.yml. Copier uses that
file during copier update to remember the choices made when the project was
created and to apply later template changes consistently.
- Git 2 or newer
- Python 3.10 or newer
- Copier 9.6 or newer
- Copier Template-Extensions 0.3.2 or newer
Install Copier with uv or pipx:
# uv
uv tool install copier --with copier-template-extensions
# pipx
pipx install copier
pipx inject copier copier-template-extensionsUse the GitHub shorthand or a Git URL as the template source:
# HTTPS through the GitHub shorthand.
copier copy --trust gh:sztal/python-package-template ./path/to/project
# SSH.
copier copy --trust git@github.com:sztal/python-package-template ./path/to/projectCopier asks for the visible project choices and applies defaults for derived values. Important choices include:
project_nameandproject_description: project metadata and documentationtarget_python: the Ruff and mypy target, frompy310throughpy314; the default ispy314package_layout: flat,src, or namespace package layoutentry_pointsandcli: optional command-line support and its frameworkfeatures: task automation, documentation, and doctestsdocs: Zensical when documentation is enabledcode_of_conduct: whether to generateCODE_OF_CONDUCT.md
Names, repository URLs, package paths, documentation URLs, copyright details, and similar values are derived from the answers. The Git name and email used as defaults come from the local Git configuration.
To provide answers non-interactively, pass Copier data explicitly:
copier copy --trust \
--data project_name=example \
--data project_description='An example package' \
--data target_python=py314 \
gh:sztal/python-package-template ./exampleRun this command from a generated project:
copier update --trustCopier compares the saved .copier-answers.yml with the current template and
updates files while preserving project-specific changes where possible. Review
the resulting diff before committing an update, especially when changing
package layout or documentation settings.
This is the template manifest and answer schema. It controls:
- the minimum Copier version;
- the
template/source directory; - the
.jinjasuffix used by template files; - the Jinja extensions loaded during rendering;
- files that should be skipped or preserved;
- the post-generation message;
- interactive answers, defaults, choices, conditional answers, and derived values.
Answers with when: false are internal implementation values. For example,
package_name, package_root, and repository_url are derived from visible
answers and are used by templates without asking the user to enter them.
This directory contains the files that can appear in a generated project.
Files ending in .jinja are rendered as Jinja templates; ordinary files are
copied as-is. Jinja expressions such as {{ package_name }} substitute an
answer, while {% if docs %} and similar blocks include content only when a
feature is enabled.
Some filenames contain Jinja expressions too. For example:
template/{% if docs == 'zensical' %}zensical.toml{% endif %}.jinja
When docs is zensical, Copier renders this as zensical.toml. When another
documentation option is selected, the filename renders to an empty name and
the file is not generated. This is why conditional filenames are used instead
of putting an unused configuration file in every generated project.
The same mechanism is used for noxfile.py, zensical.toml, the optional Code
of Conduct, CLI framework files, and the package source tree.
The root zensical.toml is not a generated-project template. It configures Zensical for this repository's own documentation site: site metadata, navigation, theme features, icons, fonts, social links, and Markdown extensions.
The generated-project version is a separate conditional file:
template/{% if docs == 'zensical' %}zensical.toml{% endif %}.jinja
That file is copied only when a generated project chooses Zensical and points its site metadata at that generated project's repository and documentation.
The extensions/ package provides small Jinja helpers used by copier.yaml:
git.py: reads Git identity values for author and maintainer defaults;slugify.py: derives distribution and import-safe package names;today.py: supplies the current date for generated metadata.
These helpers are loaded through _jinja_extensions in copier.yaml.
The template produces a complete project workflow rather than only a package directory:
pyproject.toml: Hatchling build backend, Git-tag versioning throughuv-dynamic-versioning, project metadata, dependency groups, Ruff, pytest, coverage, mypy, and Towncrier settings;src/, a flat package directory, or a namespace package directory: selected bypackage_layout;tests/: pytest fixtures and version checks;noxfile.py: test, lint, type-check, and documentation sessions using uv;.pre-commit-config.yaml: Ruff checking and formatting plus repository hygiene hooks;Makefile: short commands for installation, testing, linting, formatting, coverage, and builds;docs/andzensical.toml: optional Zensical documentation support;.github/workflows/: CI and release workflows;AGENTS.mdand.github/skills/: generated repository context for coding agents, including a first-run inspection checklist;scripts/release.py: Towncrier and Git-tag release automation;LICENSE,CHANGELOG.md, and optionalCODE_OF_CONDUCT.md.
The generated build uses Git tags as its version source. A release tag such as
v1.2.3 produces package metadata with version 1.2.3.
After changing the template, render representative projects before committing:
uv run copier copy --trust --defaults \
--data project_name=smoke-test \
--data project_description='Template smoke test' \
. /tmp/smoke-testAt minimum, check the default flat layout, a src layout, a namespace layout,
both documentation backends, and the optional Code of Conduct. For generated
projects, initialize Git, create a version tag, and run:
uv build
uv run pytest
nox -s lint
nox -s typecheckWhen changing a template filename or answer, inspect the generated tree and
the .copier-answers.yml file. A small filename expression can determine
whether an entire component exists in the output.