skip to content
Simone Gigante Gassó
Python logo in the center with many computers around

One Template to Rule Them All: Managing Python Projects with Cookiecutter and Cruft

/ 5 min read

Table of Contents

Create Projects Faster and Keep Them Consistent

Starting a new Python project often means repeating the same setup work: creating a Dockerfile, configuring Poetry, organizing folders, adding linting rules, and wiring up development tools. Some projects need small variations, but most of the structure usually stays the same.

Doing that by hand every time is slow and error-prone. That is where Cookiecutter helps. And once you have many projects generated from the same template, Cruft helps you keep them updated.

What is Cookiecutter?

Cookiecutter is a Python tool for generating projects from templates using Jinja syntax. Even though it is a Python package, you can use it to scaffold projects in many languages.

Creating Projects with Cookiecutter

There are two common ways to use Cookiecutter:

  • Install and use an existing template.
  • Build your own custom template.

This article focuses on the second approach.

To install Cookiecutter, run one of the following:

Terminal window
poetry add cookiecutter
Terminal window
pip install cookiecutter
Terminal window
conda install -c conda-forge cookiecutter

Once installed, you can start building your template.

Typical Project Structure

Here is an example of the Python project layout I often reuse:

Python project sample

This scaffold lets me create a new project in seconds. It includes:

  • A package directory with scripts and utils, where the application logic lives.
  • A .devcontainer directory for Docker and development container configuration.
  • A config directory for configuration files.
  • A data directory for datasets, images, and text files.
  • Repository-level files such as pyproject.toml, .pre-commit-config.yaml, and ignore files.

These pieces appear in many of my projects, and most of their setup is shared.

Building a Custom Cookiecutter Template

To create a template, add a cookiecutter.json file to your template repository. This file defines the values Cookiecutter will ask for when generating a new project.

{
"project_name": "Name of the project",
"project_slug": "Slug of the project",
"package_name": "Name of the package",
"repository_url": "URL of the project",
"project_description": "Description of the project"
}

This example is intentionally simple, but it is enough for many projects. Each value acts as the default for the prompt shown during project creation.

Cookiecutter uses Jinja syntax, so you can reference values like this:

{{ cookiecutter.project_slug }}

In my case, the template uses directories such as {{ cookiecutter.project_slug }} and {{ cookiecutter.project_name }}. Everything inside those folders becomes part of the generated project.

My custom Cookiecutter template, project-wizard, looks like this:

Custom cookiecutter

For example, I generate a README automatically from the values defined in cookiecutter.json:

# {{ cookiecutter.project_name }}
{{ cookiecutter.project_description }}

In files such as pyproject.toml, I also define packages and tooling that I want in every new repository. The scripts folder can include utility scripts I reuse often. This is where Cookiecutter starts saving real time.

Creating a New Project

Once the template is ready, generate a project with:

Terminal window
cookiecutter .

If the template is hosted on GitHub, you can also run:

Terminal window
cookiecutter https://github.com/simoncraf/project-wizard.git

Cookiecutter will then prompt you for the values defined in cookiecutter.json. The default values appear in brackets.

Cookiecutter questions

At this point you can create as many projects as you want from the same template. The next problem is maintenance: once the template evolves, keeping all generated projects aligned becomes tedious. That is the problem Cruft solves.

What is Cruft?

Cruft is a Python package that keeps generated projects in sync with the Cookiecutter template they came from.

Linking a Project with Cruft

Start by installing Cruft:

Terminal window
poetry add cruft
Terminal window
pip install cruft
Terminal window
conda install -c conda-forge cruft

Then add a .cruft.json file to your Cookiecutter template:

{
"template": "https://github.com/simoncraf/project-wizard",
"commit": "069a78495a5d0cbffc8245f42bd190adaa1df698",
"checkout": "null",
"context": {
"cookiecutter": {
"project_name": "{{cookiecutter.project_name }}",
"project_slug": "{{cookiecutter.project_slug }}",
"package_name": "{{cookiecutter.package_name }}",
"project_short_description": "{{cookiecutter.project_description }}",
"repository_url": "{{cookiecutter.repository_url }}",
"_template": "https://github.com/simoncraf/project-wizard"
}
},
"directory": null
}

This file stores the information Cruft needs to track the template. The two most important fields are:

  • template, which points to the Cookiecutter template repository.
  • commit, which identifies the template version used by the project.

When the template changes, its latest commit changes too. Cruft compares the project’s recorded commit with the template and tells you when an update is available.

In my custom template, I also use a helper script to make sure the commit stored in .cruft.json matches the latest template commit:

def get_last_commit_hash():
"""
Update the commit hash in `.cruft.json` to the latest commit.
"""
result = subprocess.run(["git", "log", "-n", "1", "--format=%H"], stdout=subprocess.PIPE)
hash_ = result.stdout.decode().strip()
file_path = Path("{{cookiecutter.project_slug}}") / ".cruft.json"
with file_path.open("r", encoding="utf-8") as f:
data = json.load(f)
data["commit"] = hash_
with file_path.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=4)
return hash_

If you already created a project from Cookiecutter but it does not include .cruft.json, you can link it manually:

Terminal window
cruft link https://github.com/simoncraf/project-wizard

By default, this points the project to the latest template commit and creates the .cruft.json file for you.

Updating a Project

Once a project is linked, check for updates with:

Terminal window
cruft check

If there are no updates, you will see a success message:

Cruft success message

If the template has changed, Cruft will show that an update is available:

Cruft failure message

To apply template updates, run:

Terminal window
cruft update

Cruft lets you inspect changes, skip them, or apply them directly. After applying updates, it rewrites the corresponding files automatically.

Changes made by cruft

If there are conflicts, Cruft will create .rej files. These work similarly to patch rejects and help you review what could not be merged automatically. Once you resolve or reject the changes, the commit field in .cruft.json is updated to the latest template version.

Resources

Helpful links:

Ready-to-use Cookiecutter templates worth exploring: