Daniel Roy Greenfeld

Daniel Roy Greenfeld

About | Articles | Books | Jobs | News | Tags

Cookiecutter: Project Templates Made Easy

2013-8-17

Yesterday, Jeff Knupp wrote an amazing how-to article called "Open Sourcing a Python Project the Right Way". While I was reading it, I was rather pleased by just how close it is to my own practices. Considering Jeff's amazing Writing Idiomatic Python, it meant I was on the right track.

The downside, of course, is implementation. Creating reusable Python packages has always been annoying. There are no defined/maintained best practices (especially for setup.py), so you end up cutting and pasting hacky, poorly understood, often legacy code from one project to the other. Some of it does nothing and some of it fails catastrophically on Python 3. There's a term for this sort of behavior, and it's called Cargo Cult programming.

Fortunately, while I was ranting and Jeff (and Hynek Schlawack) was writing, someone was making cookiecutter.

cookiecutter does one thing and it does it well

What cookiecutter does is make creating and maintaining project templates easy and intuitive. This allow developers of all languages (not just Python) the ability to break free from cargo-cult configuration and follow patterns dictated by the experts who present their own cookiecutter templates. So if you don't like how the author of cookiecutter's creates her projects, you can use someone else's or roll your own.

Okay, enough talk, let's use cookiecutter to build a Python project. Assuming you have virtualenv installed:

$ pip install cookiecutter

note: In the works is a Homebrew package, and possibly packages for the various Linux distributions as well.

Done? Okay, now use cookiecutter to create your Python project. For this example, I'm going to create a sample project called "cheese".:

$ cookiecutter https://github.com/audreyr/cookiecutter-pypackage.git
Cloning into 'cookiecutter-pypackage'...
remote: Counting objects: 183, done.
remote: Compressing objects: 100% (100/100), done.
remote: Total 183 (delta 87), reused 161 (delta 70)
Receiving objects: 100% (183/183), 29.36 KiB | 0 bytes/s, done.
Resolving deltas: 100% (87/87), done.
Checking connectivity... done
full_name (default is "Audrey Roy")? Daniel Greenfeld
project_name (default is "your project")? cheese
... snip for brevity

See how it asks my full name? Well, at this point, cookiecutter begins to ask a number of questions. These questions are actually specified in the cookiecutter.json file for cookiecutter-pypackage.

Once you've answered everything that cookiecutter-pypackage wants, it generates your project. Let's go and check:

$ tree cheese
cheese/
├── AUTHORS.rst
├── CONTRIBUTING.rst
├── HISTORY.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── docs
│   ├── Makefile
│   ├── authors.rst
│   ├── conf.py
│   ├── contributing.rst
│   ├── history.rst
│   ├── index.rst
│   ├── installation.rst
│   ├── make.bat
│   ├── readme.rst
│   └── usage.rst
├── requirements.txt
├── setup.py
├── simplicity
│   ├── __init__.py
│   └── simplicity.py
├── tests
│   ├── __init__.py
│   └── test_simplicity.py
└── tox.ini

While there are some differences from Jeff Knupp's example in his article (ReStructuredText vs Markdown, location of tests, etc), I would argue that the general vision is the same. Better yet, if Jeff (or someone) wants to implement Jeff's pattern, they can.

In fact...

Creating cookiecutter templates is easy and intuitive

All you have to do is:

  1. Fork cookiecutter-pypackage and rename it.
  2. Make the changes you desire. You can change anything you want, the setup.py, the test handling, or perhaps add or remove from the questions specified in cookiecutter.json. Right now repo_name is a mandatory cookiecutter.json field, but there is an issue submitted to have that changed.
  3. Remember that renders everything in Jinja2. Questions asked by cookiecutter.json are rendered to the project's files (be those files in Python, Javascript, HTML, etc). So if you add a field to cookiecutter.json, all you have to do to see it in a templates is write:
# Place in Python, HTML. Javascript, CSS, Markdown, or any other plaintext format.
{{cookiecutter.my_new_field}}
  1. Submit a pull request to cookiecutter asking for their project to be listed on the README.

It's not hard. In fact, there is already a growing ecosystem of cookiecutter templates, including Python, Flask, Django and JQuery templates.

Note: There is already a fork of cookiecutter-pypackage that even more closely matches Jeff Knupp's design.

Additional cookiecutter features

Here are more things to like about cookiecutter:

cookiecutter is focused

It doesn't handle deployment, serving of HTTP, testing, or anything else. All it does is project templates. It follows those classic words, "It's programmed to do one thing and do it well".

Supports all modern versions of Python

  • Python 2.6
  • Python 2.7
  • Python 3.3
  • Even PyPy!

cookiecutter is modular

It's not built off a single giant function, or a complex architecture. Instead, it's comprised of a number of relatively simple functions. Why? Well this way you can import easily elements of cookiecutter into other projects, and it plays into the next feature:

cookiecutter is tested

The project has as of August 20th 2013, 98% test coverage, with an intention to increase it to 100%. This makes handling the following things much easier/safer:

  1. Implementing new features without breaking existing ones.
  2. Handling new versions of Python as they emerge.

cookiecutter isn't just for Python packages

That's correct. While at the moment there is only cookiecutter-jquery, there is nothing to stop developers from using cookiecutter to create templates for anything. The way it renders output is designed to accommodate customizations for any tool.

Which brings me to my next point...

cookiecutter isn't just for Python developers

Even if you don't know Python you can use cookiecutter. The templating is done via Jinja2, which isn't far off from other template languages like Mustache, Handlebars, or Liquid. if you are worried about collisions between templating systems, just use Jinja2's {% raw %} template tag:

{# Jinja2's raw template to escape the Liquid template inside #}
{% raw %} {# Liquid template from here on #}
<ul id="products">
{% for product in products %}
<li>
  <h2>{{ product.title }}</h2>
  Only {{ product.price | format_as_money }}

  <p>{{ product.description | prettyprint | truncate: 200  }}</p>

</li>
{% endfor %}
</ul>
{% endraw %}

Cookiecutter logo

  • Update 09/20/2013: Test coverage increased to 98% from 91%.

Tags: python django rant flask pypi pypy python3 javascript audrey cookiecutter
← Back to home