Copier template for modern Python packages. This repository is the template engine and its source material, not one of the packages generated by it. The repository's historical changelog is no longer maintained; generated projects can use their own changelog and release tooling.
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.13 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 documentationintegration_branch: the integration branch name, defaulting tomasterchangelog: enable changelog and Towncrier support (default: enabled); fragments are written at integration-branch merge time from the incoming branch's Git history and assembled for releasesminimum_python: the minimum supported version, from3.13through3.15(default:3.13); CI tests through Python 3.15target_python: the default interpreter and Ruff and mypy target, frompy313throughpy315(default:py315); it must be at least the minimum version. When targeting a newer version, ensure tool suggestions and code remain compatible with the minimum supported version.pytest_testmon:no(default),manualto enablepytest --testmon, orautomaticto use testmon by default; coverage targets always disable it when installed so they run the full suitepackage_layout: flat (default) orsrcsource layoutpackage_type: standard (default) or implicit namespace package; namespace packages also require a top-levelnamespace_namethat is a Python identifier and does not conflict with generatedtestsorscriptspackagesentry_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 minimum_python=3.13 \
--data target_python=py315 \
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.
Existing projects using main should set integration_branch=main when
updating; the new default is master. Review existing changelog files when
disabling changelog support, as project-specific files may be preserved.
For projects generated with the former package_layout: namespace option,
Copier defaults to a flat namespace package on update. This corrects the
previously built top-level package to use the qualified import name
namespace_name.package_name; update downstream imports and review the
generated distribution before releasing it. When overriding both the layout
and namespace name during that update, also set package_type=namespace.
Projects using a target_python below py313 must select a supported target
when updating. Existing saved targets are retained; the new minimum_python
answer defaults to 3.13. Review .python-version and tooling changes on update.
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 optional Towncrier settings;- a flat or
src/package directory, optionally under an implicit namespace: selected independently bypackage_layoutandpackage_type; 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: commands for installation, testing, linting, type checking, coverage, distribution validation, documentation, and release preparation;docs/andzensical.toml: optional Zensical documentation support;.github/workflows/: shared CI checks, optional Pages deployment, and explicitly dispatched package publication;AGENTS.mdand.github/skills/: generated repository context and on-demand workflows for development, audits, wiki notes, package builds, branch integration, and releases, including a first-run inspection checklist;scripts/andRELEASING.md: distribution checks, local Git-tag release preparation, and explicit Trusted Publishing guidance;LICENSE, optionalCHANGELOG.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,
documentation and task automation both enabled and disabled, and the optional
Code of Conduct. For generated projects, initialize Git, create a version tag,
and run:
uv build
uv run pytest
make lint
make 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.