A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pre/post generation hook system and Jinja2 template extensions for enhanced templating capabilities. This module provides extensibility through custom scripts and enhanced Jinja2 template functionality.
Execute custom scripts before and after project generation.
def run_hook(hook_name, project_dir, context):
"""
Find and execute hook from project directory.
Parameters:
- hook_name: str - Name of hook to run ('pre_gen_project' or 'post_gen_project')
- project_dir: str - Generated project directory path
- context: dict - Template context available to hook script
"""
def run_hook_from_repo_dir(repo_dir, hook_name, project_dir, context, delete_project_on_failure):
"""
Run hook from repo directory with cleanup.
Parameters:
- repo_dir: str - Template repository directory
- hook_name: str - Hook name to execute
- project_dir: str - Generated project directory
- context: dict - Template context
- delete_project_on_failure: bool - Whether to cleanup on hook failure
"""
def run_pre_prompt_hook(repo_dir):
"""
Run pre_prompt hook from repo directory.
Parameters:
- repo_dir: str - Template repository directory
Returns:
str - Path to repository directory (may be modified by hook)
"""Find and validate hook scripts in template directories.
def valid_hook(hook_file, hook_name):
"""
Determine if hook file is valid.
Parameters:
- hook_file: str - Path to hook file
- hook_name: str - Expected hook name
Returns:
bool - True if hook file is valid and executable
"""
def find_hook(hook_name, hooks_dir='hooks'):
"""
Find hook scripts in directory.
Parameters:
- hook_name: str - Name of hook to find
- hooks_dir: str - Directory to search for hooks
Returns:
str or None - Path to hook script if found
"""Execute hook scripts with proper context and error handling.
def run_script(script_path, cwd='.'):
"""
Execute script from working directory.
Parameters:
- script_path: str - Path to script file
- cwd: str - Working directory for script execution
"""
def run_script_with_context(script_path, cwd, context):
"""
Execute script after Jinja rendering.
Parameters:
- script_path: str - Path to script template file
- cwd: str - Working directory for execution
- context: dict - Context for rendering script template
"""Enhanced templating capabilities through custom Jinja2 extensions.
class JsonifyExtension(Extension):
"""Converts Python objects to JSON."""
class RandomStringExtension(Extension):
"""Creates random ASCII strings."""class SlugifyExtension(Extension):
"""Slugifies strings for use in URLs and filenames."""
class UUIDExtension(Extension):
"""Generates UUID4 strings."""class TimeExtension(Extension):
"""Handles dates and times with 'now' tag."""Enhanced Jinja2 environments for template processing.
class ExtensionLoaderMixin:
"""Mixin for loading Jinja2 extensions from context."""
class StrictEnvironment(ExtensionLoaderMixin, Environment):
"""Strict Jinja2 environment that raises errors on undefined variables."""EXIT_SUCCESS: int # Success exit status (0)Create hook scripts in your template's hooks/ directory:
hooks/pre_gen_project.py:
#!/usr/bin/env python
"""Pre-generation hook script."""
import sys
# Validate user input
project_name = '{{cookiecutter.project_name}}'
if not project_name.replace('-', '').replace('_', '').isalnum():
print('ERROR: Project name must be alphanumeric (with hyphens/underscores)')
sys.exit(1)
print(f'✓ Pre-generation validation passed for: {project_name}')hooks/post_gen_project.py:
#!/usr/bin/env python
"""Post-generation hook script."""
import os
import subprocess
# Initialize git repository
if '{{cookiecutter.initialize_git}}' == 'yes':
subprocess.run(['git', 'init'], check=True)
subprocess.run(['git', 'add', '.'], check=True)
subprocess.run(['git', 'commit', '-m', 'Initial commit'], check=True)
print('✓ Git repository initialized')
# Install dependencies
if '{{cookiecutter.install_dependencies}}' == 'yes':
subprocess.run(['pip', 'install', '-e', '.'], check=True)
print('✓ Dependencies installed')from cookiecutter.hooks import run_hook, run_pre_prompt_hook, find_hook
# Run pre-prompt hook
repo_dir = './my-template'
modified_repo_dir = run_pre_prompt_hook(repo_dir)
# Find and validate hooks
pre_hook = find_hook('pre_gen_project', hooks_dir='./template/hooks')
if pre_hook:
print(f"Found pre-generation hook: {pre_hook}")
# Run post-generation hook
context = {
'cookiecutter': {
'project_name': 'my-project',
'initialize_git': 'yes',
'install_dependencies': 'no'
}
}
run_hook(
hook_name='post_gen_project',
project_dir='./generated-project',
context=context
)Templates can use enhanced Jinja2 functionality:
Template file example:
{# Generate unique identifier #}
PROJECT_ID = "{{ cookiecutter.project_name | uuid4 }}"
{# Create URL-friendly slug #}
URL_SLUG = "{{ cookiecutter.project_name | slugify }}"
{# Generate random secret key #}
SECRET_KEY = "{{ cookiecutter.project_name | random_ascii_string(50) }}"
{# Convert complex data to JSON #}
CONFIG = {{ cookiecutter.database_config | jsonify }}
{# Current timestamp #}
CREATED_AT = "{% now 'utc', '%Y-%m-%d %H:%M:%S' %}"from cookiecutter.environment import StrictEnvironment
from cookiecutter.extensions import JsonifyExtension, SlugifyExtension
# Create environment with custom extensions
env = StrictEnvironment()
env.add_extension(JsonifyExtension)
env.add_extension(SlugifyExtension)
# Use environment for template rendering
template = env.from_string('{{ project_name | slugify }}')
result = template.render(project_name='My Awesome Project')
# Returns: 'my-awesome-project'from cookiecutter.hooks import run_hook_from_repo_dir, valid_hook
import os
# Validate hook before execution
hook_path = './template/hooks/pre_gen_project.py'
if os.path.exists(hook_path) and valid_hook(hook_path, 'pre_gen_project'):
print("Hook is valid and executable")
# Run hook with cleanup on failure
try:
run_hook_from_repo_dir(
repo_dir='./template',
hook_name='pre_gen_project',
project_dir='./output/my-project',
context=context,
delete_project_on_failure=True
)
except Exception as e:
print(f"Hook execution failed: {e}")from cookiecutter.extensions import (
JsonifyExtension,
SlugifyExtension,
RandomStringExtension,
UUIDExtension,
TimeExtension
)
from jinja2 import Environment
# Set up environment with all extensions
env = Environment()
env.add_extension(JsonifyExtension)
env.add_extension(SlugifyExtension)
env.add_extension(RandomStringExtension)
env.add_extension(UUIDExtension)
env.add_extension(TimeExtension)
# Example template using extensions
template = env.from_string("""
Project: {{ name | slugify }}
ID: {{ name | uuid4 }}
Secret: {{ name | random_ascii_string(32) }}
Config: {{ config | jsonify }}
Created: {% now 'utc', '%Y-%m-%d' %}
""")
result = template.render(
name="My Cool Project",
config={"debug": True, "port": 8000}
)from cookiecutter.hooks import run_hook
from cookiecutter.exceptions import FailedHookException
try:
run_hook(
hook_name='post_gen_project',
project_dir='./my-project',
context=context
)
except FailedHookException as e:
print(f"Hook failed: {e}")
# Optionally clean up generated files
import shutil
shutil.rmtree('./my-project')from cookiecutter.hooks import find_hook
import os
hooks_dir = './template/hooks'
hook_types = ['pre_prompt', 'pre_gen_project', 'post_gen_project']
available_hooks = {}
for hook_type in hook_types:
hook_path = find_hook(hook_type, hooks_dir)
if hook_path:
available_hooks[hook_type] = hook_path
print("Available hooks:")
for hook_type, path in available_hooks.items():
print(f" {hook_type}: {path}")Install with Tessl CLI
npx tessl i tessl/pypi-cookiecutter