Hooks
Cookiecutter hooks are scripts executed at specific stages during the project generation process. They are either Python or shell scripts, facilitating automated tasks like data validation, pre-processing, and post-processing. These hooks are instrumental in customizing the generated project structure and executing initial setup tasks.
Types of Hooks
Hook |
Execution Timing |
Working Directory |
Template Variables |
Version |
---|---|---|---|---|
pre_prompt |
Before any question is rendered. |
A copy of the repository directory |
No |
2.4.0 |
pre_gen_project |
After questions, before template process. |
Root of the generated project |
Yes |
0.7.0 |
post_gen_project |
After the project generation. |
Root of the generated project |
Yes |
0.7.0 |
Creating Hooks
Hooks are added to the hooks/
folder of your template. Both Python and Shell scripts are supported.
Python Hooks Structure:
cookiecutter-something/
├── {{cookiecutter.project_slug}}/
├── hooks
│ ├── pre_prompt.py
│ ├── pre_gen_project.py
│ └── post_gen_project.py
└── cookiecutter.json
Shell Scripts Structure:
cookiecutter-something/
├── {{cookiecutter.project_slug}}/
├── hooks
│ ├── pre_prompt.sh
│ ├── pre_gen_project.sh
│ └── post_gen_project.sh
└── cookiecutter.json
Python scripts are recommended for cross-platform compatibility. However, shell scripts or .bat files can be used for platform-specific templates.
Hook Execution
Hooks should be robust and handle errors gracefully. If a hook exits with a nonzero status, the project generation halts, and the generated directory is cleaned.
Working Directory:
pre_prompt
: Scripts run in the root directory of a copy of the repository directory. That allows the rewrite ofcookiecutter.json
to your own needs.pre_gen_project
andpost_gen_project
: Scripts run in the root directory of the generated project, simplifying the process of locating generated files using relative paths.
Template Variables:
The pre_gen_project
and post_gen_project
hooks support Jinja template rendering, similar to project templates. For instance:
module_name = '{{ cookiecutter.module_name }}'
Examples
Pre-Prompt Sanity Check:
A pre_prompt
hook, like the one below in hooks/pre_prompt.py
, ensures prerequisites, such as Docker, are installed before prompting the user.
import sys
import subprocess
def is_docker_installed() -> bool:
try:
subprocess.run(["docker", "--version"], capture_output=True, check=True)
return True
except Exception:
return False
if __name__ == "__main__":
if not is_docker_installed():
print("ERROR: Docker is not installed.")
sys.exit(1)
Validating Template Variables:
A pre_gen_project
hook can validate template variables. The following script checks if the provided module name is valid.
import re
import sys
MODULE_REGEX = r'^[_a-zA-Z][_a-zA-Z0-9]+$'
module_name = '{{ cookiecutter.module_name }}'
if not re.match(MODULE_REGEX, module_name):
print(f'ERROR: {module_name} is not a valid Python module name!')
sys.exit(1)
Conditional File/Directory Removal:
A post_gen_project
hook can conditionally control files and directories. The example below removes unnecessary files based on the selected packaging option.
import os
REMOVE_PATHS = [
'{% if cookiecutter.packaging != "pip" %}requirements.txt{% endif %}',
'{% if cookiecutter.packaging != "poetry" %}poetry.lock{% endif %}',
]
for path in REMOVE_PATHS:
path = path.strip()
if path and os.path.exists(path):
os.unlink(path) if os.path.isfile(path) else os.rmdir(path)