Skip to content

Latest commit

 

History

History

README.md

python-package-template

Copier Latest release CI License mypy Ruff uv

Copier template for modern Python packages. This repository is the template engine and its source material, not one of the packages generated by it.

What Copier Does

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.

Requirements

Install Copier with uv or pipx:

# uv
uv tool install copier --with copier-template-extensions

# pipx
pipx install copier
pipx inject copier copier-template-extensions

Generate A Project

Use 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/project

Copier asks for the visible project choices and applies defaults for derived values. Important choices include:

  • project_name and project_description: project metadata and documentation
  • target_python: the Ruff and mypy target, from py310 through py314; the default is py314
  • package_layout: flat, src, or namespace package layout
  • entry_points and cli: optional command-line support and its framework
  • features: task automation, documentation, and doctests
  • docs: Zensical when documentation is enabled
  • code_of_conduct: whether to generate CODE_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 ./example

Update A Project

Run this command from a generated project:

copier update --trust

Copier 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.

Template Mechanics

copier.yaml

This is the template manifest and answer schema. It controls:

  • the minimum Copier version;
  • the template/ source directory;
  • the .jinja suffix 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.

template/

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.

zensical.toml in this repository

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.

Extensions

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.

Generated Components

The template produces a complete project workflow rather than only a package directory:

  • pyproject.toml: Hatchling build backend, Git-tag versioning through uv-dynamic-versioning, project metadata, dependency groups, Ruff, pytest, coverage, mypy, and Towncrier settings;
  • src/, a flat package directory, or a namespace package directory: selected by package_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/ and zensical.toml: optional Zensical documentation support;
  • .github/workflows/: CI and release workflows;
  • AGENTS.md and .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 optional CODE_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.

Working On This Template

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-test

At 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 typecheck

When 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.