CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cookiecutter

A command-line utility that creates projects from project templates, e.g. creating a Python package project from a Python package project template.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

hooks-extensions.mddocs/

Hooks and Extensions

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.

Capabilities

Hook System

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)
    """

Hook Discovery and Validation

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
    """

Script Execution

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
    """

Jinja2 Extensions

Enhanced templating capabilities through custom Jinja2 extensions.

JSON and Data Extensions

class JsonifyExtension(Extension):
    """Converts Python objects to JSON."""

class RandomStringExtension(Extension):
    """Creates random ASCII strings."""

String Processing Extensions

class SlugifyExtension(Extension):
    """Slugifies strings for use in URLs and filenames."""

class UUIDExtension(Extension):
    """Generates UUID4 strings."""

Date and Time Extensions

class TimeExtension(Extension):
    """Handles dates and times with 'now' tag."""

Environment Classes

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."""

Hook Constants

EXIT_SUCCESS: int  # Success exit status (0)

Usage Examples

Hook Implementation

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')

Using Hooks Programmatically

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
)

Jinja2 Extensions Usage

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' %}"

Custom Environment Setup

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'

Advanced Hook Usage

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}")

Extension Usage in Python

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}
)

Hook Error Handling

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')

Dynamic Hook Discovery

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

docs

configuration.md

hooks-extensions.md

index.md

main-api.md

repository-handling.md

template-processing.md

user-interaction.md

utilities-exceptions.md

tile.json