Skip to content

Developing tu-cookbook-python

This page is for people contributing to this repository — the Copier template and the tu-cookbook CLI — not for people writing notebooks inside a cookbook that was generated from it. If that's what you're after, see the CONTRIBUTING.md that ships inside a generated cookbook (template/CONTRIBUTING.md in this repo).

Project Layout

tu-cookbook-python/
├── copier.yml               # prompts asked by `copier copy`
├── extensions.py            # Jinja filters/globals used by copier.yml and the template
├── justfile                 # dev commands for THIS repo
├── pyproject.toml           # this repo's own package: the `tu-cookbook` CLI
├── src/tu_cookbook_python/  # the CLI itself
│   ├── cmd/new.py             # `tu-cookbook new` — scaffolds a notebook in a cookbook
│   ├── cmd/fix_urls.py        # `tu-cookbook fix-urls`
│   ├── _git.py                 # git remote parsing helpers
│   └── _writer.py
├── template/                 # the Copier template — copied into every generated cookbook
│   ├── Justfile                 # a DIFFERENT justfile, shipped to end users
│   ├── CONTRIBUTING.md          # a DIFFERENT contributing guide, for cookbook authors
│   ├── pyproject.toml.jinja
│   └── ...
├── tests/
│   ├── template/               # renders the template and asserts on the output
│   └── tu_cookbook_python/     # unit tests for the CLI
├── docs/                      # this documentation site (zensical)
└── scripts/                   # maintainer tooling (dependency sync)

The core idea: this repo is a template factory, not a cookbook. Nothing under template/ runs directly here — it's rendered by Copier into a new git repository. Everything else (src/, tests/, docs/, extensions.py, copier.yml) exists to build, test, and document that template and its companion CLI.

How the Pieces Fit Together

  • copier.yml defines the questions copier copy asks (project_name, license, editor, ...) and registers the Jinja extensions used while rendering.
  • extensions.py provides the custom Jinja filters/globals referenced from copier.yml and the template files themselves — e.g. cookbookify, slugify, git_user_name. The root-level test_extensions.py unit-tests these directly, independent of any template rendering.
  • template/ is the actual template. File and directory names can themselves contain Jinja ({% if use_license %}LICENSE{% endif %}.jinja), and any .jinja-suffixed file is rendered before being written into the generated project.
  • src/tu_cookbook_python/ is a separate, installable CLI (tu-cookbook) that generated cookbooks depend on for day-to-day tasks, such as scaffolding a new notebook (tu-cookbook new, wired up as the new recipe in template/Justfile) or fixing Binder URLs (tu-cookbook fix-urls).

justfile — This Repo's Dev Commands

Important

This repo's root `justfile` is a **different file** from `template/Justfile`. The root one runs commands against *this* repository; `template/Justfile` is itself a template file that gets rendered into every generated cookbook. Don't confuse recipes between the two when reading either file.

Run just to list all recipes. Some worth knowing:

  • just venv — uv sync this repo's own dependencies (copier, pytest, ruff, ty, zensical, ...)
  • just check — lint, format, and type-check (ruff check, ruff format, ty check)
  • just test — run the test suite
  • just render — render the template into ./rendered with a fixed set of example answers, for manually inspecting generated output
  • just docs — serve this documentation site locally
  • just hooks-install / just hooks — install / run the pre-commit hooks

How the Tests Work

tests/ splits along the same line as the codebase itself:

  • tests/template/ tests the template, not Python code. tests/conftest.py defines a copied_project fixture that runs copier.run_copy() against this repo (vcs_ref="HEAD") with a fixed set of answers, producing a real rendered project in a temp directory once per test module. Tests then assert against the rendered files:
  • test_copier.py checks the full generated file tree against an explicit allow-list, and spot-checks that Jinja substitutions (project name, author, license, ...) landed correctly in files like CITATION.cff, myst.yml, and pyproject.toml.
  • test_copy_licenses.py checks that each license choice renders the correct LICENSE text.
  • test_jinja_tags.py scans every file under template/ for {{ variable }} tags and asserts each one is actually defined in copier.yml (plus a small allow-list for just/ruff syntax that looks similar) — this catches typo'd template variables that would otherwise only surface when someone actually runs copier copy.
  • tests/tu_cookbook_python/ are ordinary unit tests for the CLI package in src/, exercising git-remote parsing, Binder URL construction, and the new/fix-urls commands directly — no template rendering involved.

Run everything with just test, or target one half directly with uv run pytest tests/template / uv run pytest tests/tu_cookbook_python.

Maintainer-Only

The following are run by maintainers, not typical contributors:

  • just release <target> runs the tests, bumps the version (uv version), regenerates CHANGELOG.md from Conventional Commits, commits, and tags. Pushing is left to the maintainer (git push && git push --tags).
  • scripts/update_template_dependencies.py (wired up as the _update recipe) scans template/ for pinned dependency versions — per rules declared in scripts/dependency_rules.json — and bumps them to the latest release on PyPI, committing the result. This keeps every newly-generated cookbook's pyproject.toml / environment.yml on current dependency versions without a maintainer doing it by hand.