Skip to main content

Sprout

Tests PyPI version Supported versions Downloads license

Sprout is a Jinja2-based project generator with a Python manifest.

Instead of configuring prompts in YAML, you write sprout.py:

from sprout import Question

questions = [
    Question(key="project_name", prompt="Project name"),
]

That single manifest drives interactive prompts, CLI flags, validation and conditional questions.

Template files go in template/. .jinja files are rendered, everything else is copied.

Works with local templates, Git repos, or owner/repo GitHub shorthand.

Every question becomes a CLI flag so you can script it too:

sprout new <template-path> <project-path>
sprout new <template-path> <project-path> --project-name demo

Install

Install globally with uv:

uv tool install sprout-template

Or run directly without installing using uvx:

uvx --from sprout-template sprout <command>

Usage

sprout init [directory]
sprout add <template-source> [--name <trusted-name>]
sprout list
sprout new <template> <project-path> [--force] [--<question-flag> <value> ...]

new accepts a local template path, Git URL, owner/repo GitHub shorthand, or a trusted name added with sprout add. Pass values for question flags to skip those prompts:

sprout new <template-path> <project-path> --project-name demo

Use sprout new <template> --help to show template-specific flags.

Initialize a template

Create a minimal sprout.py and template/README.md.jinja scaffold in the current directory:

sprout init

Pass a directory to initialize it elsewhere. Existing scaffold files are never overwritten.

Trusted templates

Store a reusable name for any supported template source:

sprout add zigai/python-project-template --name py
sprout new py ./my-project
sprout list

Template structure

The source root must contain sprout.py.

The only required name is questions.

from sprout import Question

questions = [
    Question(key="project_name", prompt="Project name"),
]

Optional names are template_dir, style, extensions, title, cli_boolean_style, should_skip_file(...), and apply(context).

Question model

Each Question describes one answer:

from sprout import Question

Question(
    key="project_name",
    prompt="Project name",
    help="Used for package metadata and generated paths",
    default="demo",
    metavar="name",
)

`key` is the answer dictionary key. It also becomes the CLI flag name.

```text
project_name -> --project-name

Choices

Use choices when the answer should come from a closed list:

from sprout import Question

questions = [
    Question(
        key="package_manager",
        prompt="Package manager",
        choices=[("uv", "uv"), ("pip", "pip")],
        default="uv",
    ),
]

Multiselect

from sprout import Question

questions = [
    Question(
        key="workflow",
        prompt="Workflows",
        choices=[("tests", "Tests"), ("lint", "Lint")],
        multiselect=True,
    ),
]

From the CLI:

sprout new <template-path> <project-path> --workflow tests --workflow lint

Booleans

Use the built-in yes/no helper:

from sprout import Question

questions = [
    Question.yes_no(
        key="git_init",
        prompt="Initialize Git?",
        default=True,
    ),
]

By default, yes/no questions are exposed as Boolean CLI flags:

sprout new <template-path> <project-path> --git-init
sprout new <template-path> <project-path> --no-git-init

In --help, paired boolean flags are documented using bracket negation: --[no-]git-init.

If a template should use explicit yes/no values instead, opt into that style in sprout.py:

cli_boolean_style = "yes-no"

Then the CLI accepts values for yes/no questions:

sprout new <template-path> <project-path> --git-init yes
sprout new <template-path> <project-path> --git-init no

Conditional flow

when can be a boolean or a callable that receives the answers collected so far:

from sprout import Question

questions = [
    Question.yes_no(
        key="create_github_repo",
        prompt="Create GitHub repository?",
        default=False,
    ),
    Question(
        key="github_repo_visibility",
        prompt="GitHub repository visibility",
        choices=[("private", "Private"), ("public", "Public")],
        default="private",
        when=lambda answers: bool(answers.get("create_github_repo")),
    ),
]

Defaults, parsers, and validators

Defaults can be static values or callables. Use default="" for a text prompt that should accept a blank answer.

from sprout import Question

questions = [
    Question(
        key="package_name",
        prompt="Package name",
        default=lambda answers: str(answers["project_name"]).replace("-", "_"),
    ),
]

Validators return (valid, message):

from sprout import Question, validate_repository_url

questions = [
    Question(
        key="repository_url",
        prompt="Repository URL",
        validators=[validate_repository_url],
    ),
]

Sprout includes validators for repository URLs, GitHub repository URLs, repository names, npm package names, and semantic versions.

Destination-aware questions

When question definitions need runtime context, make questions callable:

from pathlib import Path
from jinja2 import Environment
from sprout import Question


def questions(env: Environment, destination: Path) -> list[Question]:
    return [
        Question(
            key="project_name",
            prompt="Project name",
            default=destination.name,
        ),
    ]

The callable must accept exactly two positional parameters: env and destination.

Rendering

Default rendering uses template_dir, or template when no directory is declared.

template_dir = "template"

Skipping files

should_skip_file receives a path relative to template_dir and the final answers:

from sprout import NO_LICENSE


def should_skip_file(relative_path: str, answers: dict[str, object]) -> bool:
    return relative_path == "LICENSE.jinja" and answers.get("license") == NO_LICENSE

Jinja environment

Set Jinja2 extension classes with extensions:

from sprout import CurrentYearExtension, GitDefaultsExtension

extensions = [GitDefaultsExtension, CurrentYearExtension]

When extensions is omitted, the default environment includes Git defaults:

  • git_user_name
  • git_user_email
  • github_username

Include GitDefaultsExtension explicitly when you provide a custom extension list and still want those globals.

CurrentYearExtension exposes:

  • current_year

Prompt title and style

title = "Generate a Python package"
from sprout import ManifestContext


def title(context: ManifestContext) -> str | None:
    return f"Generate project in {context.destination}"

The title is evaluated before answers are collected. For prompt appearance, assign style to a sprout.CompactStyle (the default) or sprout.RailStyle instance.

Custom generation

Most templates should use the default renderer, but you can define apply(context) when generation needs custom file creation, post-processing, or post-generation actions.

from sprout import ManifestContext, render_templates


def apply(context: ManifestContext):
    return render_templates(
        context.env,
        context.template_dir,
        context.destination,
        context.answers,
        render_paths=True,
    )

apply must accept exactly one context parameter. It may return None, one path, or a sequence of paths for the generated-files summary.

Programmatic APIs raise SproutError subclasses for expected operational failures. Catch the specific error when recovery differs by failure type, or catch SproutError at an application boundary. The sprout CLI translates these errors into concise process-exit messages.

Examples

License

MIT

Metadata

Release files for sprout-template 1.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sprout-template 1.6.0
File Size Uploaded Provenance Actions
sprout_template-1.6.0.tar.gz 52.3 kB Without provenance Details for sprout_template-1.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sprout-template 1.6.0
File Interpreter ABI Platform Actions
sprout_template-1.6.0-py3-none-any.whl Python 3 none any Details for sprout_template-1.6.0-py3-none-any.whl

Total release size: 115.5 kB

Release files / sprout_template-1.6.0.tar.gz

Download URL sprout_template-1.6.0.tar.gz
Size 52.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d2e08eaa0d140f1117b93192b6a1fa8f139d8930987483bc775f9dc8e0ddd3b6
BLAKE2b-256 checksum
How to use checksums
2841eb1befc0caf03bcf22008508185670d3fdfda3d26b522573c960944ed44f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Release files / sprout_template-1.6.0-py3-none-any.whl

Download URL sprout_template-1.6.0-py3-none-any.whl
Size 63.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9f85c75b3bb10657d81a57e8ea9e6ef62b3613a5f4eddf47a4a3c1b5804552a9
BLAKE2b-256 checksum
How to use checksums
8ac7566c1c21f24863a18cbfa902b8e30f93e41a10057f6847b768abfda025b4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.16

Security for sprout-template 1.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page