Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cliffignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Skip commits by their SHA1

e8759bdeb573cd9304d1fb6e0501906ea9cb865a
13 changes: 9 additions & 4 deletions .example-input.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,25 @@ author_email: hasansezertasan@gmail.com
short_description: A Python project example.
include_cli: false
include_web: false
web_framework: fastapi
include_tui: false
include_gui: false
include_mcp: false
include_worker: false
worker_broker: kafka
include_c_extensions: false
include_profiling: false
include_pycrucible: false
include_pydantic_settings: false
release_automation: none
dependency_management: none
include_changelog: false
include_contributing: false
include_code_of_conduct: false
include_citation: false
include_pants: false
include_codecov: false
include_mise: false
include_trunk: false
include_poe: false
include_commitizen: false
use_precommit: false
include_precommit: false
include_dbeaver: false
include_vpn: false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,4 @@ __marimo__/
### Generated by the author
src/**/_version.py
mise.local.toml
NOTES.md
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
# Pre-commit: uv run --locked tox run -e pre-commit
# Build: uv build
# Run CLI: uv run --locked <repo-name> version
# Run FastAPI: uv run --locked fastapi dev <repo-name>.web:app
# Run FastAPI: uv run --locked fastapi dev <repo-name>.web.app:app
# Serve docs: uv run --only-group docs mkdocs serve
# Profile: uv run --locked tox run -e profile (if profiling enabled)
# Trunk check: trunk check (if configured)
# Pants lint: pants lint :: (if configured)
```
Expand All @@ -27,4 +28,4 @@
- **Structure**: Code in `src/{{github_repo_name}}/`, tests in `tests/`
- **Tools**: Ruff (full rules, complexity ≤5), MyPy/Pyright strict, pytest + coverage, pre-commit
- **Template**: `.jinja` files, `{{variables}}`, `{% raw %}` for Jinja escaping
- **Optional Tools**: Poe (poethepoet) for task running, configurable via `include_poe`; Commitizen for version management, configurable via `include_commitizen`
- **Optional Tools**: mise for task running (configurable via `include_mise`); Commitizen for version management (configurable via `include_commitizen`)
225 changes: 165 additions & 60 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
This is a **Copier template** for generating modern Python packages with comprehensive tooling. The `template/` directory contains Jinja2-templated files that are rendered when users run `copier copy` to scaffold new Python projects.

Key architecture:

- Template files use `.jinja` extension and contain variables like `{{github_repo_name}}`, `{{author_full_name}}`, etc.
- Template variables are defined in `copier.yml`
- The `example/` directory shows a rendered example using `.example-input.yml` as answers
Expand All @@ -25,14 +26,19 @@ copier copy --data-file .example-input.yml --defaults . /tmp/test-project

# Actually generate the example project
copier copy --data-file .example-input.yml --defaults . example/

# Force regenerate (overwrites existing)
copier copy --data-file .example-input.yml --defaults . example/ --force

# Test with specific features enabled
copier copy --data-file .example-input.yml --data include_worker=true --data worker_broker=kafka --defaults --trust . /tmp/test-worker --force
```

### Working with Generated Projects

Commands that would run in a generated project (useful for testing):
Commands for the `example/` directory (or any generated project):

```bash
# Navigate to example directory
cd example/

# Install dependencies
Expand All @@ -48,12 +54,27 @@ uv run --locked tox run -e style
uv run --locked tox run -e 3.10
uv run --locked tox run -e 3.14

# Run the CLI
uv run --locked <repo-name> version
uv run --locked <repo-name> info
# Run a single test file
uv run --locked pytest tests/test_smoke.py -v

# Run the CLI (if include_cli=true)
uv run --locked example version
uv run --locked example info

# Run the FastAPI/Litestar web app (if include_web=true)
uv run --locked fastapi dev example.web.app:app

# Run the FastAPI web app
uv run --locked fastapi dev <repo-name>.web:app
# Run the TUI (if include_tui=true)
uv run --locked example-tui

# Run the GUI (if include_gui=true)
uv run --locked example-gui

# Run the MCP server (if include_mcp=true)
uv run --locked example-mcp

# Run the worker (if include_worker=true)
uv run --locked example-worker

# Run pre-commit hooks
uv run --locked tox run -e pre-commit
Expand All @@ -63,6 +84,9 @@ uv run --locked tox run -e docs-build

# Serve docs locally
uv run --locked tox run -e docs-server

# Profiling (if include_profiling=true)
uv run --locked tox run -e profile
```

### Linting and Pre-commit
Expand All @@ -77,66 +101,115 @@ pre-commit install

## Template Architecture

### Jinja2 Template Structure

The template uses Copier's Jinja2 templating with these key patterns:
### Jinja2 Template Patterns

1. **Variable Substitution**: `{{github_repo_name}}` renders to the user's repo name
2. **Dynamic File Paths**: `src/{{github_repo_name}}/` creates package directories with user-provided names
3. **TODO Markers**: `TODO @{{github_user}}:` generates actionable TODOs in the rendered project
4. **Raw Blocks**: `{% raw %}${{ github.ref_name }}{% endraw %}` preserves GitHub Actions syntax
5. **Conditional Files**: `{% if condition %}filename{% endif %}.jinja` includes file only when condition is true
6. **Whitespace Control**: Use `{%-` and `-%}` to avoid extra blank lines in rendered output

### Template Variables (copier.yml)

Required inputs:

- `github_user`, `github_repo_name`, `author_full_name`, `author_email`, `short_description`

Optional components (all boolean):

- `include_cli` - Typer CLI
- `include_web` - Web app + Dockerfile (framework choice: FastAPI or Litestar)
- `include_gui` - Tkinter GUI
- `include_tui` - Textual TUI
- `include_mcp` - MCP (Model Context Protocol) server
- `include_worker` - FastStream message queue worker
- `include_c_extensions` - Cython support with multi-platform wheels
- `include_profiling` - py-spy, scalene, cProfile tools
- `include_pycrucible` - PyCrucible for standalone executables
- `include_pydantic_settings` - pydantic-settings for config

Framework/broker choices (when parent option is enabled):

- `web_framework` - fastapi/litestar (when `include_web` is true)
- `worker_broker` - kafka/nats/rabbitmq/redis (when `include_worker` is true)

Devcontainer services:

- `include_dbeaver` - CloudBeaver database management UI
- `include_vpn` - OpenVPN client

Tooling options:

- `release_automation` - none/release-please/release-it/release-drafter
- `dependency_management` - none/renovate/dependabot
- `include_pants`, `include_codecov`, `include_trunk`
- `include_mise` - mise for tool version management and task running
- `include_commitizen` - Commitizen versioning
- `include_precommit` - pre-commit hooks

### Generated Project Structure

Projects created from this template have:
Root modules in `src/{{github_repo_name}}/`:

- **CLI Application**: Typer-based CLI in `cli.py.jinja` with `version` and `info` commands
- **Web Application**: FastAPI stub in `web.py.jinja` with `/version` and `/info` endpoints
- **Core Modules**:
- `__metadata__.py.jinja`: Project name constant
- `dirs.py.jinja`: Platform-aware directory management using `platformdirs`
- `config.py.jinja`: Configuration placeholder
- `logging_setup.py.jinja`: Centralized logging setup
- `core.py` and `utils.py`: Empty stubs for user code
- **Entry Points**: Both CLI (`project.scripts`) and GUI (`project.gui-scripts`) entry points configured in `pyproject.toml`
- `__init__.py`, `__main__.py`, `__metadata__.py` - Package setup
- `_version.py` - Auto-generated by hatch-vcs (excluded from coverage)

### Dependency Groups
Subpackages (each with `__init__.py` and `app.py`):

Generated projects use uv dependency groups:
- `core/` - Core infrastructure (always included):
- `dirs.py` - Platform-aware directories
- `logging_setup.py` - Centralized logging
- `config.py` - Configuration (uses pydantic-settings if enabled)
- `utils/` - Utility functions (always included)
- `cli/` - Typer CLI with `version` and `info` commands (conditional)
- `web/` - FastAPI/Litestar with `/version` and `/info` endpoints (conditional)
- `gui/` - Tkinter GUI launcher (conditional)
- `tui/` - Textual TUI (conditional)
- `mcp/` - MCP server with `version` and `info` tools (conditional)
- `worker/` - FastStream message queue worker (conditional)

- `dev`: Development dependencies (includes tox, tox-uv, style, test)
- `style`: Linting/formatting tools (ruff, mypy, pyright, ty, pyrefly, vulture, slotscheck, taplo, validate-pyproject, typos, actionlint)
- `test`: Testing tools (pytest, coverage, pytest-xdist, pytest-rerunfailures, pytest-mock, pytest-dependency)
- `docs`: MkDocs for documentation
- `tool`: Project management tools (commitizen, copier, poethepoet)
- `pre-commit`: Pre-commit framework and uv integration
Other conditional files:

### Build System
- `_c_extension.pyx`, `.pxd`, `.pyi` - Cython extension files
- `profile.py` - Profiling script

- **Builder**: `hatchling` with `hatch-vcs` plugin
- **Versioning**: Git-based versioning (tags) via hatch-vcs, fallback to 0.1.0
- **Version File**: Auto-generated `_version.py` (excluded from coverage)
- **Build Command**: `uv build` creates wheels and sdist
Test packages mirror source structure in `tests/`:

### CI/CD Workflows
- `tests/cli/`, `tests/web/`, `tests/gui/`, `tests/tui/`, `tests/mcp/`, `tests/worker/` (each conditional)

Generated projects include GitHub Actions:
Entry points configured in `pyproject.toml`:

1. **CI** (`.github/workflows/ci.yml.jinja`):
- Runs `uv run --locked tox run` on Windows, Ubuntu, macOS
- Matrix tests across all supported Python versions (3.10-3.14)
- Triggered on push to main/master and PRs
- `project.scripts`: `pkg.cli.app:app`, `pkg.tui.app:main`, `pkg.web.app:main`, `pkg.mcp.app:main`, `pkg.worker.app:main`
- `project.gui-scripts`: `pkg.gui.app:main` (launches without terminal)

2. **CD** (`.github/workflows/cd.yml.jinja`):
- Triggered on GitHub Release publication
- Builds with `uv build`
- Publishes to PyPI using Trusted Publishing (`uv publish --trusted-publishing always`)
- Attaches build artifacts to GitHub Release
- Requires `publish` environment with id-token write permissions
### Devcontainer Structure

3. **Other Workflows**:
- `check-pr-title.yml.jinja`: Validates PR titles follow conventional commits
- `release-drafter.yml.jinja`: Auto-generates release notes
The `.devcontainer/docker-compose.yml.jinja` consolidates all services:

- `devcontainer` - Main development container (always included)
- Message broker services (conditional on `include_worker` + `worker_broker`):
- `kafka` - Bitnami Kafka (KRaft mode)
- `nats` - NATS with JetStream
- `rabbitmq` - RabbitMQ with management UI
- `redis` - Redis
- `cloudbeaver` - DBeaver CloudBeaver (conditional on `include_dbeaver`)
- `vpn` - OpenVPN client (conditional on `include_vpn`)

### Build System

- **Builder**: `hatchling` with `hatch-vcs` plugin
- **Versioning**: Git tags via hatch-vcs, fallback to 0.1.0
- **Build Command**: `uv build`

### CI/CD Workflows

1. **CI** (`ci.yml.jinja`): Matrix tests on Windows/Ubuntu/macOS, Python 3.10-3.14
- Codecov coverage steps are conditional on `include_codecov`
2. **CD** (`cd.yml.jinja`): PyPI trusted publishing on GitHub Release
3. **Build Wheels** (`build-wheels.yml`): Multi-platform Cython wheels (conditional on `include_c_extensions`)
4. **Release automation**: release-please, release-it, or release-drafter (configurable)
5. **PR title linting**: Validates conventional commits format

### PyPI Trusted Publishing Setup

Expand All @@ -150,14 +223,37 @@ For generated projects to publish to PyPI:
- Workflow: `cd.yml`
- Environment: `publish`

## Code Style Guidelines

- **Imports**: Absolute only, grouped stdlib→third-party→local (ruff enforced)
- **Formatting**: Ruff, 88 chars, LF endings
- **Types**: Strict typing required, `from typing import ...`
- **Naming**: snake_case vars/functions, PascalCase classes, UPPER_CASE constants
- **Complexity**: Max cyclomatic complexity of 5 (ruff mccabe)
- **Structure**: Code in `src/{{github_repo_name}}/`, tests in `tests/`

## Template Modification Guidelines

### Adding New Template Files

1. Create file in `template/` with `.jinja` extension
2. Use `{{variable_name}}` for Copier variable substitution
3. Test by regenerating `example/` directory
4. Update this CLAUDE.md if the file introduces new architecture
2. For conditional files, use `{% if condition %}filename{% endif %}.jinja` naming
3. Use `{{variable_name}}` for Copier variable substitution
4. Test by regenerating `example/` directory

### Adding New Optional Components

1. Add boolean variable to `copier.yml` with `type: bool`, `default`, and `help`
2. If component has choices (like `worker_broker`), add a separate choice variable with `when: "{{ parent_option }}"`
3. Create conditional directory: `template/src/{{github_repo_name}}/{% if include_xxx %}xxx{% endif %}/`
4. Create matching test directory: `template/tests/{% if include_xxx %}xxx{% endif %}/`
5. Update `pyproject.toml.jinja`:
- Add optional dependency
- Add to `all` extras
- Add entry point if applicable
- Add keywords
6. Update `.example-input.yml` with the new option
7. Update `README.md` with documentation

### Modifying Template Variables

Expand All @@ -166,22 +262,31 @@ For generated projects to publish to PyPI:
3. Update all `.jinja` files that reference the variable
4. Regenerate example to verify

### Important Template Conventions
### Template Conventions

- **Escaping**: Use `{% raw %}{% endraw %}` to preserve Jinja2 syntax in generated files (e.g., for GitHub Actions)
- **File Naming**: Dynamic directories use `{{variable}}` format (e.g., `src/{{github_repo_name}}/`)
- **Escaping**: Use `{% raw %}{% endraw %}` for GitHub Actions syntax
- **Comments**: Template-only comments use `{# #}`, generated comments use `#`
- **TODOs**: Use `TODO @{{github_user}}:` pattern for actionable items in generated projects
- **TODOs**: Use `TODO @{{github_user}}:` pattern for actionable items
- **Whitespace**: Use `{%-` and `-%}` to eliminate blank lines from conditionals

### Testing Template Changes

Always test changes by:

1. Regenerating the example: `copier copy --data-file .example-input.yml --defaults . example/ --force`
2. Running style checks in example: `cd example && uv run --locked tox run -e style`
3. Running tests in example: `cd example && uv run --locked tox run`
2. Running style checks: `cd example && uv run --locked tox run -e style`
3. Running tests: `cd example && uv run --locked tox run`

For features with choices (like `worker_broker`), test multiple combinations:
```bash
copier copy --data-file .example-input.yml --data include_worker=true --data worker_broker=kafka --defaults --trust . /tmp/test-kafka --force
copier copy --data-file .example-input.yml --data include_worker=true --data worker_broker=rabbitmq --defaults --trust . /tmp/test-rabbitmq --force
```

## Copier-Specific Behavior

- **Subdirectory**: `_subdirectory: template` in `copier.yml` means Copier renders from `template/` not root
- **Answers File**: `{{_copier_conf.answers_file}}.jinja` becomes `.copier-answers.yml` for update tracking
- **Update Workflow**: Users can run `copier update` to pull template changes into their project
- **Subdirectory**: `_subdirectory: template` means Copier renders from `template/` not root
- **Answers File**: `{{_copier_conf.answers_file}}.jinja` becomes `.copier-answers.yml`
- **Update Workflow**: Users run `copier update` to pull template changes
- **Post-copy Task**: `git init` runs automatically after scaffolding
- **Trust Flag**: Use `--trust` when testing locally to run post-copy tasks
Loading