Skip to content

About

Configuration and CLIs for Python projects based on Pydantic and Pydantic Settings.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

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. The repository's historical changelog is no longer maintained; generated projects can use their own changelog and release tooling.

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
  • integration_branch: the integration branch name, defaulting to master
  • changelog: 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 releases
  • minimum_python: the minimum supported version, from 3.13 through 3.15 (default: 3.13); CI tests through Python 3.15
  • target_python: the default interpreter and Ruff and mypy target, from py313 through py315 (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), manual to enable pytest --testmon, or automatic to use testmon by default; coverage targets always disable it when installed so they run the full suite
  • package_layout: flat (default) or src source layout
  • package_type: standard (default) or implicit namespace package; namespace packages also require a top-level namespace_name that is a Python identifier and does not conflict with generated tests or scripts packages
  • 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 minimum_python=3.13 \
  --data target_python=py315 \
  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.

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.

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 optional Towncrier settings;
  • a flat or src/ package directory, optionally under an implicit namespace: selected independently by package_layout and package_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/ and zensical.toml: optional Zensical documentation support;
  • .github/workflows/: shared CI checks, optional Pages deployment, and explicitly dispatched package publication;
  • AGENTS.md and .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/ and RELEASING.md: distribution checks, local Git-tag release preparation, and explicit Trusted Publishing guidance;
  • LICENSE, optional 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, 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 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.

About

Configuration and CLIs for Python projects based on Pydantic and Pydantic Settings.

Resources

Code of conduct

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages