Development Setup
Guide for setting up a development environment to contribute to the Cookiecutter Python Package template itself.
Prerequisites
Required Software
- Python 3.9+ (3.11+ recommended)
- Git for version control
- Cookiecutter for testing template generation
Optional but Recommended
- GitHub CLI for easier repository management
- VS Code with recommended extensions
- Docker for testing in isolated environments
Getting Started
1. Fork and Clone
# Fork the repository on GitHub, then clone your fork
git clone https://github.com/YOUR_USERNAME/cookiecutter-python-package.git
cd cookiecutter-python-package
# Add upstream remote
git remote add upstream https://github.com/s-celles/cookiecutter-python-package.git
2. Development Environment Setup
Create Virtual Environment
# Create virtual environment
python -m venv venv
# Activate virtual environment
# Linux/macOS
source venv/bin/activate
# Windows (PowerShell)
venv\Scripts\Activate.ps1
# Windows (Command Prompt)
venv\Scripts\activate.bat
Install Development Dependencies
# Install the template in development mode
pip install -e .[dev]
# Verify installation
cookiecutter --version
3. Development Tools Setup
Pre-commit Hooks
# Install pre-commit hooks
pre-commit install
# Run all hooks on all files (optional)
pre-commit run --all-files
VS Code Configuration
If using VS Code, install recommended extensions: - Python - Ruff - YAML - Jinja2 - GitLens
Development Workflow
Testing Template Changes
Basic Template Testing
# Test template generation with default values
cookiecutter . --no-input
# Test with specific configuration
cookiecutter . --no-input --config-file tests/configs/minimal.json
# Test interactively
cookiecutter .
Comprehensive Testing
# Run all tests
pytest
# Run specific test categories
pytest tests/test_template.py
pytest tests/test_hooks.py
pytest tests/test_validation.py
# Run with coverage
pytest --cov=. --cov-report=html
Testing Generated Projects
# Generate test project
cookiecutter . --no-input --output-dir /tmp/test-output
# Test the generated project
cd /tmp/test-output/my_python_package
python -m venv venv
source venv/bin/activate
pip install -e .[dev]
pytest
Code Quality Checks
Linting and Formatting
# Format code
ruff format .
# Check for linting issues
ruff check .
# Fix auto-fixable issues
ruff check --fix .
Type Checking
Security Scanning
Template Structure
Key Files and Directories
cookiecutter-python-package/
├── cookiecutter.json # Template configuration
├── hooks/ # Pre/post generation hooks
│ └── post_gen_project.py # Post-generation processing
├── tests/ # Template tests
│ ├── configs/ # Test configurations
│ ├── test_template.py # Template generation tests
│ ├── test_hooks.py # Hook functionality tests
│ └── test_validation.py # Input validation tests
├── docs/ # Template documentation
├── {{cookiecutter.project_slug}}/ # Template directory
│ ├── src/ # Package source code template
│ ├── tests/ # Test template
│ ├── docs/ # Documentation template
│ ├── .github/ # GitHub workflows template
│ ├── pyproject.toml.jinja # Project configuration template
│ └── README.md.jinja # README template
├── pyproject.toml # Template project configuration
├── .pre-commit-config.yaml # Pre-commit configuration
└── README.md # Template documentation
Template Files
Template files use Jinja2 syntax:
Variable Substitution
{# In {{cookiecutter.project_slug}}/pyproject.toml.jinja #}
[project]
name = "{{cookiecutter.project_slug}}"
description = "{{cookiecutter.project_short_description}}"
authors = [
{name = "{{cookiecutter.full_name}}", email = "{{cookiecutter.email}}"},
]
Conditional Content
{# Include section only if tool is enabled #}
{% if cookiecutter.use_mypy == 'y' -%}
[tool.mypy]
python_version = "{{cookiecutter.python_requires.split('>=')[1]}}"
strict = true
{%- endif %}
File Inclusion/Exclusion
Files are conditionally included based on template variables:
- Files with .jinja
extension are processed as templates
- Files without conditions are always included
- Post-generation hooks can remove unwanted files
Hooks
post_gen_project.py
This script runs after template generation and: - Removes unused files based on tool selection - Sets up Git repository - Installs dependencies (if requested) - Initializes pre-commit hooks
#!/usr/bin/env python3
"""Post-generation hook for cookiecutter template."""
import os
import shutil
from pathlib import Path
# Get template variables
use_mypy = "{{cookiecutter.use_mypy}}" == "y"
use_mkdocs = "{{cookiecutter.use_mkdocs}}" == "y"
# Remove files based on configuration
if not use_mypy:
# Remove MyPy configuration
pass
if not use_mkdocs:
# Remove MkDocs files
if Path("mkdocs.yml").exists():
os.remove("mkdocs.yml")
if Path("docs").exists():
shutil.rmtree("docs")
Writing Tests
Test Categories
Template Generation Tests
# tests/test_template.py
import pytest
from cookiecutter.main import cookiecutter
def test_default_generation(tmp_path):
"""Test template generation with default values."""
result = cookiecutter(
".",
no_input=True,
output_dir=str(tmp_path)
)
project_dir = tmp_path / "my_python_package"
assert project_dir.exists()
assert (project_dir / "pyproject.toml").exists()
assert (project_dir / "src").exists()
def test_minimal_configuration(tmp_path):
"""Test minimal tool configuration."""
result = cookiecutter(
".",
no_input=True,
output_dir=str(tmp_path),
extra_context={
"use_mypy": "n",
"use_bandit": "n",
"use_safety": "n",
}
)
project_dir = tmp_path / "my_python_package"
pyproject = (project_dir / "pyproject.toml").read_text()
# Verify minimal configuration
assert "[tool.mypy]" not in pyproject
assert "[tool.bandit]" not in pyproject
Hook Tests
# tests/test_hooks.py
def test_post_gen_hook_removes_unused_files(tmp_path):
"""Test that post-generation hook removes unused files."""
result = cookiecutter(
".",
no_input=True,
output_dir=str(tmp_path),
extra_context={"use_mkdocs": "n"}
)
project_dir = tmp_path / "my_python_package"
# Verify MkDocs files were removed
assert not (project_dir / "mkdocs.yml").exists()
assert not (project_dir / "docs").exists()
Validation Tests
# tests/test_validation.py
def test_invalid_project_name():
"""Test validation of project name."""
with pytest.raises(Exception):
cookiecutter(
".",
no_input=True,
extra_context={"project_name": "invalid-name!"}
)
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=. --cov-report=html --cov-report=term
# Run specific test file
pytest tests/test_template.py
# Run with verbose output
pytest -v
# Run and stop on first failure
pytest -x
# Run tests matching pattern
pytest -k "test_default"
Documentation Updates
Updating Documentation
When making changes that affect users:
- Update relevant documentation pages in
docs/
- Update README.md if the change affects basic usage
- Update CHANGELOG.md following Keep a Changelog format
- Test documentation builds locally
# Install documentation dependencies
pip install mkdocs mkdocs-material
# Serve documentation locally
mkdocs serve
# Build documentation
mkdocs build
Documentation Guidelines
- Use clear, concise language
- Include code examples for new features
- Update navigation in
mkdocs.yml
if adding new pages - Test all code examples
- Use consistent formatting and style
Contributing Guidelines
Pull Request Process
-
Create feature branch from main
-
Make changes following code style guidelines
-
Add tests for new functionality
-
Update documentation as needed
-
Run quality checks
-
Commit with clear messages
-
Push and create pull request
Commit Message Format
Follow conventional commits:
- feat:
- New features
- fix:
- Bug fixes
- docs:
- Documentation changes
- test:
- Test additions or modifications
- refactor:
- Code refactoring
- style:
- Code style changes
- ci:
- CI/CD changes
- chore:
- Maintenance tasks
Code Style Guidelines
- Follow PEP 8 (enforced by Ruff)
- Use type hints where appropriate
- Write clear docstrings
- Keep functions focused and small
- Use meaningful variable names
- Add comments for complex logic
Release Process
Preparing a Release
- Update version in relevant files
- Update CHANGELOG.md with release notes
- Create release PR and get approval
- Tag release after merging
- Create GitHub release with release notes
Testing Before Release
# Test template with multiple configurations
for config in tests/configs/*.json; do
echo "Testing $config"
cookiecutter . --no-input --config-file "$config" --output-dir /tmp/test-releases
done
# Test generated projects
cd /tmp/test-releases
for dir in */; do
echo "Testing generated project: $dir"
cd "$dir"
python -m venv venv
source venv/bin/activate
pip install -e .[dev]
pytest
cd ..
done
This development setup ensures that contributions to the template maintain high quality and don't break existing functionality.