Skip to main content

A CLI that composes single-file Cookiecutter templates via Git

Project description

tpl – Template Puller CLI

tpl treats single-file Cookiecutter templates like versioned building blocks. Instead of copy/pasting snippets or maintaining giant boilerplate repos, point tpl at any Git tag (GitHub, Bitbucket, or a local directory), drop the rendered file into your project, and record the provenance in .tpl-lock.toml. When a new tag ships, tpl upgrade tells you exactly what changed and keeps your tweaks safe.

Install note: The PyPI package is published as tpl-cli (the CLI binary is still tpl). Install it via uv (uv sync --package tpl-cli) or pip (pip install tpl-cli).

Why tpl?

  • Composable building blocks – pull one template at a time or describe an entire starter kit in tpl.toml. Every block is still a normal Cookiecutter repo, with tags for versioning and hooks for customization.
  • Deterministic upgrades – tpl stores the source URL, tag, entry file, checksum, and context for every managed file. Upgrades re-render using the same context, detect local edits, and produce .tpl-new / .tpl.diff artifacts (or run git merge-file when --merge is set).
  • Works offline – reference local:/path/to/repo@tag entries to test unreleased templates or run integration tests without hitting the network.
  • Zero vendor lock-in – tpl orchestrates Git + Cookiecutter. Templates live wherever you keep them; tpl just fetches, renders, and tracks provenance.
# Pull a single template
uv run tpl pull gh:you/tpl-logging@v0.3.0 --file infra/logging.yaml=logging.yaml --set project_slug=my-app

# Compose an entire starter kit
uv run tpl compose gh:you/python-service@v1.2.0 --set project_name=my-app

# Reapply a local tpl.toml (hierarchical blocks + shared context)
uv run tpl apply --config tpl.toml --force

# Detect drift / upgrade safely
uv run tpl status
uv run tpl upgrade --merge

Feature highlights

  • Single-file template pullstpl pull grabs any tagged Cookiecutter repo and writes the requested entry file. Supports overrides via --set key=value.
  • Project compositiontpl compose gh:user/repo@tag runs every block defined in tpl.toml, sharing context variables across blocks and supporting nested kind = "project" entries.
  • Local configs – keep tpl.toml inside your repo and run tpl apply --config tpl.toml so teammates can rehydrate the same files without remembering commands.
  • Hierarchical layering – include other project templates (Docker layer, CI layer, etc.) to build multi-stage starter kits.
  • Upgrade safety – checksum tracking, optional three-way merge (--merge), and consistent conflict artifacts.
  • Private repo support – authenticate with GitHub or Bitbucket tokens; tpl passes creds to Git via GIT_ASKPASS so secrets never hit disk.

Quick start (uv)

  1. Install uv and ensure Python 3.12 exists: uv python install 3.12.

  2. Sync dependencies (grab the dev extras for tooling):

    uv sync --extra dev
    
  3. Run commands through uv to pick up the managed .venv:

    uv run tpl --help
    uv run pytest
    

Template authoring

tpl works with standard Cookiecutter repositories—no extra metadata files. A typical repo looks like this:

tpl-logging/
├── cookiecutter.json
└── {{cookiecutter.project_slug}}/
    └── logging.yaml
  • cookiecutter.json declares the variables tpl can override via --set key=value.
  • Everything inside {{cookiecutter.project_slug}}/ renders normally. tpl copies whichever file you request through --file dest=entry or via the project configuration.
  • Tag releases using normal Git tags (e.g., git tag v0.3.0). tpl records the tag you pulled so tpl upgrade --to v0.4.0 knows which version to fetch next.
  • You can keep additional files in the same repo and reference each one as a separate entry, which tpl will render in a single pass.

Daily workflow

A three-step workflow: create → pull → upgrade

1. Author a template once

Build a standard Cookiecutter repo (tpl-logging):

tpl-logging/
├── cookiecutter.json
└── {{cookiecutter.project_slug}}/
    └── logging.yaml

cookiecutter.json might look like:

{
  "project_slug": "logging",
  "environment": "staging"
}

…and {{cookiecutter.project_slug}}/logging.yaml can reference those values:

version: 1
environment: {{ cookiecutter.environment }}

Tag releases with meaningful versions (git tag v0.3.0). tpl records the tag you pull so upgrades know where to fetch the next version.

2. Pull it into another project

uv run tpl pull gh:you/tpl-logging@v0.3.0 \
  --file infra/logging.yaml=logging.yaml \
  --set environment=prod

tpl clones the tagged repo into ~/.tpl-cache, renders it with the provided context, copies logging.yaml into your project, and stores the source/tag/context in .tpl-lock.toml. Now tpl status will track that file.

3. Upgrade when a new tag ships

uv run tpl upgrade infra/logging.yaml --to v0.4.0

tpl re-renders the template, checks if the file changed locally, and either overwrites it or writes <file>.tpl-new plus a <file>.tpl.diff. tpl automatically attempts a Git-like merge when it detects local edits; pass --no-merge to skip the merge step and jump straight to conflict artifacts.

Pull multiple files from one template

Need more than one output? Repeat --file DEST[=ENTRY] for each extra file:

uv run tpl pull gh:you/tpl-logging@v0.3.0 \
  --file infra/logging.yaml=logging.yaml \
  --file infra/logging-dev.yaml=logging-dev.yaml \
  --set environment=dev

tpl renders the template once, copies both files, and records each entry in .tpl-lock.toml so upgrades work the same way.

Check managed files

uv run tpl status

Outputs each tracked file plus its checksum status (OK, MODIFIED, MISSING).

Compose a remote project template

Project templates ship a tpl.toml:

name = "python-service"
version = "0.2.0"

[context]
project_name = "my-service"

[[blocks]]
source = "gh:you/logging-block@v0.1.0"

[[blocks.files]]
path = "infra/logging.yaml"
entry = "logging.yaml"

[blocks.context]
environment = "prod"

[[blocks]]
kind = "project"
source = "gh:you/docker-layer@v0.3.0"

kind = "project" layers another template, letting you stack base scaffolds, Docker bits, CI pipelines, etc. [[blocks.files]] describes each destination/entry pair you want to copy from the same template—add as many as you need.

uv run tpl compose gh:you/python-service@v0.2.0 --set project_name=my-service

Apply a local tpl.toml

Keep the same schema in your own repo to define reusable building blocks:

name = "demo"
version = "0.3.0"

[context]
project_name = "demo"

[[blocks]]
source = "gh:you/logging-block@v0.1.0"

[[blocks.files]]
path = "infra/logging.yaml"
entry = "logging.yaml"

[blocks.context]
environment = "prod"

[[blocks]]
source = "bb:team/docker-layer@v0.4.0"

[[blocks.files]]
path = "infra/docker-compose.yaml"
entry = "docker-compose.yaml"

[[blocks]]
source = "local:../templates/tpl-cache@v0.1.0"

[[blocks.files]]
path = "scripts/bootstrap.py"
entry = "scripts/bootstrap.py"

Add more [[blocks.files]] entries to copy additional files from the same template. tpl renders the template once per block and copies each requested output.

uv run tpl apply --config tpl.toml --set project_name=my-service

Upgrade everything

uv run tpl upgrade

If a file’s checksum changed, tpl attempts a Git-style merge first. If it can’t merge cleanly (or you pass --no-merge), tpl writes <file>.tpl-new and <file>.tpl.diff. Target a single file with uv run tpl upgrade path/to/file --to v0.3.0.

CLI reference

Command Description
tpl pull <repo-spec> --file dest=entry [--file ...] [--set key=value] Render a Cookiecutter repo once, copy each requested entry into your project, and record provenance in .tpl-lock.toml. Supports GitHub (gh:user/repo@tag), Bitbucket (bb:), and local:/path/to/repo@tag.
tpl status Show whether every managed file matches its stored checksum (OK, MODIFIED, MISSING).
tpl upgrade <path> [--to tag] [--no-merge] Re-render a single managed file. tpl attempts to merge local edits automatically; add --no-merge to skip merging and always emit <file>.tpl-new / .tpl.diff.
tpl compose <repo-spec> [--set key=value] [--force] Fetch a remote project template (tpl.toml), render every block, and write all configured files.
tpl apply --config tpl.toml [--set key=value] [--force] Apply a local project configuration stored in your repo (same schema as a remote tpl.toml).
tpl upgrade (no path) Reapply whichever project template is recorded in .tpl-lock.toml (remote or local). Honors --no-merge.

Project configuration (tpl.toml) reference

Minimal structure:

name = "demo"
version = "0.1.0"

[context]
project_name = "demo"

[[blocks]]
source = "gh:you/some-template@v1.0.0"

[[blocks.files]]
path = "src/config.yaml"    # Destination path in your project
entry = "config.yaml"        # File inside the rendered template (defaults to basename of path)

Key fields:

Field Where Description
name, version root Metadata only; helpful for humans/logging.
[context] root Default context values shared across every block. Overridden per block context or nested project templates.
[[blocks]] root array Each block references a Cookiecutter repo (GitHub, Bitbucket, or local). Set kind = "project" to include another tpl.toml.
source block Repo spec. Examples: gh:you/template@v1.0.0, bb:team/repo@v0.4.0, local:../templates/logging@v0.3.0.
[[blocks.files]] block Destination/entry pairs copied from that repo. Add multiple entries to copy multiple files.
path block file Destination inside your project. Directories are created automatically.
entry block file Relative file inside the rendered template. Defaults to basename(path) if omitted.
[blocks.context] or inline context = { key = "value" } block Optional overrides merged on top of the root context before rendering the block. Useful for per-file tweaks.

Repository specs

Prefix Example Notes
gh: / github: gh:openai/tpl-example@v0.1.0 HTTPS clone via GitHub. Use TPL_GITHUB_TOKEN/GITHUB_TOKEN for private repos.
bb: / bitbucket: bb:team/repo@v1.2.0 HTTPS clone via Bitbucket. Use TPL_BITBUCKET_USERNAME + TPL_BITBUCKET_APP_PASSWORD.
local: / file: local:../templates/logging@v0.1.0 Absolute or relative path to a local git repo. Great for integration tests or unpublished templates.

Authentication & state

  • GitHub – set TPL_GITHUB_TOKEN (or GITHUB_TOKEN). tpl authenticates as x-access-token automatically.
  • Bitbucket – set TPL_BITBUCKET_USERNAME and TPL_BITBUCKET_APP_PASSWORD.
  • Cookiecutter state – tpl writes replay/config data under ${HOME}/.tpl-state by default (override with TPL_COOKIECUTTER_STATE_DIR). The directory is safe to delete if you need a clean slate.

Credentials feed Git via GIT_ASKPASS, so secrets never appear in lockfiles or logs.

Automation & development

Command Description
just fmt Run ruff format.
just lint / just lint-fix Run ruff check (optionally with --fix).
just typecheck Run mypy tpl.
just test Run pytest (unit + integration).
just check Run lint, typecheck, and tests sequentially.
just precommit Run the full pre-commit stack.

Install hooks once per machine:

uv run pre-commit install

Contributing

See CONTRIBUTING.md for the full workflow (uv setup, testing expectations, release process). CI runs Ruff, mypy, pre-commit, and pytest on every PR, and tagged commits on main auto-publish to PyPI.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tpl_cli-0.1.3.tar.gz (24.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tpl_cli-0.1.3-py3-none-any.whl (19.5 kB view details)

Uploaded Python 3

File details

Details for the file tpl_cli-0.1.3.tar.gz.

File metadata

  • Download URL: tpl_cli-0.1.3.tar.gz
  • Upload date:
  • Size: 24.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for tpl_cli-0.1.3.tar.gz
Algorithm Hash digest
SHA256 dd4ff5c9bb61311023ef04b2b2816427a55be151c73f6d611050d954a8d597af
MD5 1f063726ff3c0af90695fb1c257201ee
BLAKE2b-256 2ad902ea151df21fb0370465a84d7266186fd86c7f06ce7a8ac8db3613539041

See more details on using hashes here.

Provenance

The following attestation bundles were made for tpl_cli-0.1.3.tar.gz:

Publisher: release.yml on wdonofrio/tpl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file tpl_cli-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: tpl_cli-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 19.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for tpl_cli-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 70dfbf6bfd80c119f2cc618e6a1e7b4c46a6779e098bc9c57c07b2bc86e1cde6
MD5 b233a6af1cd1b9ad39d2f3f15543402a
BLAKE2b-256 62c17d660fa05e28cd80e4ce02e6765432be8ae7024eaa830306ade551403569

See more details on using hashes here.

Provenance

The following attestation bundles were made for tpl_cli-0.1.3-py3-none-any.whl:

Publisher: release.yml on wdonofrio/tpl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page