Skip to main content

Minimal text-based prompt-loader with TOML/YAML front-matter

Project description

textprompts

Python Docs Julia Docs Go Docs Hex Docs PyPI version Python versions CI status Coverage License

So simple, it's not even worth vibe coding yet it just makes so much sense.

Tired of fancy UIs for prompt management that just make your system harder to debug? Prompts are text — keep them next to your code, let git do version control, and stop letting your formatter eat the whitespace.

Cross-Language Support

textprompts uses a cross-language compatible prompt template format:

  • Python (this package): Available on PyPI as textprompts
  • Node/TypeScript: Available in packages/textprompts-ts folder
  • Julia: Available in packages/TextPrompts.jl folder
  • Go (alpha): Available in packages/textprompts-go folder (docs)
  • Elixir: Available on Hex as textprompts (package, docs, source in packages/textprompts-ex)

All ports parse the shared testdata/sections/cases.json fixtures into the same data shape, so prompt files round-trip across your stack without conversion.

Why textprompts?

Prompts rot. Variables get renamed, flags drift away from the branches that use them, an Agent Skill ships with a {customer_tier} no one passes anymore. textprompts is built so those bugs fail loudly the moment they happen — one file holds the prompt, its variables, its feature flags, and the descriptions for all of them, validated together.

  • One file, one contract - prompt body, typed flags, variables, and their descriptions live side by side; the file is the spec
  • Conditional DSL, not f-string spaghetti - {if} / {switch} branch on declared flags; unknown flags, missing variables, and uncovered enum cases raise at format() time
  • Catches drift over time - rename a flag and every prompt that still references it breaks immediately instead of silently producing the wrong output
  • Just text - git diffs your changes, formatters can't touch it, no external UI to babysit

Installation

uv add textprompts # or pip install textprompts

Quick Start

Flexible by default - TextPrompts loads metadata when present and still works fine without it:

  1. Create a prompt file (greeting.txt):
---
title = "Customer Greeting"
version = "1.0.0"
description = "Friendly greeting for customer support"
---
Hello {customer_name}!

Welcome to {company_name}. We're here to help you with {issue_type}.

Best regards,
{agent_name}
  1. Load and use it (no configuration needed):
import textprompts

# Just load it - works with or without metadata
prompt = textprompts.load_prompt("greeting.txt")
# Or simply
alt = textprompts.Prompt.from_path("greeting.txt")

# Use it safely - all placeholders must be provided
message = prompt.prompt.format(
    customer_name="Alice",
    company_name="ACME Corp",
    issue_type="billing question",
    agent_name="Sarah"
)

print(message)

# Or use partial formatting when needed
partial = prompt.prompt.format(
    customer_name="Alice",
    company_name="ACME Corp",
    skip_validation=True
)
# Result: "Hello Alice!\n\nWelcome to ACME Corp. We're here to help you with {issue_type}.\n\nBest regards,\n{agent_name}"

# Prompt objects expose `.meta` and `.prompt`.
# Use `prompt.prompt.format()` for safe formatting or `str(prompt)` for raw text.

Even simpler - no metadata required:

# simple_prompt.txt contains just: "Analyze this data: {data}"
prompt = textprompts.load_prompt("simple_prompt.txt")  # Just works!
result = prompt.prompt.format(data="sales figures")

Core Features

Safe String Formatting

Never ship a prompt with missing variables again:

from textprompts import PromptString

template = PromptString("Hello {name}, your order {order_id} is {status}")

# ✅ Strict formatting - all placeholders must be provided
result = template.format(name="Alice", order_id="12345", status="shipped")

# ❌ This catches the error by default
try:
    result = template.format(name="Alice")  # Missing order_id and status
except ValueError as e:
    print(f"Error: {e}")  # Missing format variables: ['order_id', 'status']

# ✅ Partial formatting - replace only what you have
partial = template.format(name="Alice", skip_validation=True)
print(partial)  # "Hello Alice, your order {order_id} is {status}"

Simple & Flexible Metadata Handling

TextPrompts is designed to be flexible by default - load metadata when it's present, and fall back gracefully when it isn't. No configuration needed!

import textprompts

# Default behavior: load metadata if available, otherwise just use the file content
prompt = textprompts.load_prompt("my_prompt.txt")  # Just works!

# Three modes available for different use cases:
# 1. ALLOW (default): Load metadata if present, don't worry if it's incomplete
textprompts.set_metadata("allow")  # Flexible metadata loading (default)
prompt = textprompts.load_prompt("prompt.txt")  # Loads any metadata found

# 2. IGNORE: Treat as simple text file, use filename as title
textprompts.set_metadata("ignore")  # Super simple file loading
prompt = textprompts.load_prompt("prompt.txt")  # No metadata parsing
print(prompt.meta.title)  # "prompt" (from filename)

# 3. STRICT: Require complete metadata for production use
textprompts.set_metadata("strict")  # Prevent errors in production
prompt = textprompts.load_prompt("prompt.txt")  # Must have title, description, version

# Override per prompt when needed
prompt = textprompts.load_prompt("prompt.txt", meta="strict")

Why this design?

  • Default = Flexible: Parse metadata when it's available, no friction when it's not
  • No configuration needed: Just load files and it works
  • Production-Safe: Use strict mode to catch missing metadata before deployment

Real-World Examples

OpenAI Integration

import openai
from textprompts import load_prompt

system_prompt = load_prompt("prompts/customer_support_system.txt")
user_prompt = load_prompt("prompts/user_query_template.txt")

response = openai.chat.completions.create(
    model="gpt-4.1-mini",
    messages=[
        {
            "role": "system",
            "content": system_prompt.prompt.format(
                company_name="ACME Corp",
                support_level="premium"
            )
        },
        {
            "role": "user",
            "content": user_prompt.prompt.format(
                query="How do I return an item?",
                customer_tier="premium"
            )
        }
    ]
)

Function Calling (Tool Definitions)

Yes, you can version control your whole tool schemas too:

# tools/search_products.txt
---
title = "Product Search Tool"
version = "2.1.0"
description = "Search our product catalog"
---
{
    "type": "function",
    "function": {
        "name": "search_products",
        "description": "Search for products in our catalog",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Search query for products"
                },
                "category": {
                    "type": "string",
                    "enum": ["electronics", "clothing", "books"],
                    "description": "Product category to search within"
                },
                "max_results": {
                    "type": "integer",
                    "default": 10,
                    "description": "Maximum number of results to return"
                }
            },
            "required": ["query"]
        }
    }
}
import json
from textprompts import load_prompt

# Load and parse the tool definition
tool_prompt = load_prompt("tools/search_products.txt")
tool_schema = json.loads(tool_prompt.prompt)

# Use with OpenAI
response = openai.chat.completions.create(
    model="gpt-4.1-mini",
    messages=[{"role": "user", "content": "Find me some electronics"}],
    tools=[tool_schema]
)

Environment-Specific Prompts

import os
from textprompts import load_prompt

env = os.getenv("ENVIRONMENT", "development")
system_prompt = load_prompt(f"prompts/{env}/system.txt")

# prompts/development/system.txt - verbose logging
# prompts/production/system.txt - concise responses

Prompt Versioning & Experimentation

from textprompts import load_prompt

# Easy A/B testing
prompt_version = "v2"  # or "v1", "experimental", etc.
prompt = load_prompt(f"prompts/{prompt_version}/system.txt")

# Git handles the rest:
# git checkout experiment-branch
# git diff main -- prompts/

File Format

TextPrompts uses TOML front-matter (optional) followed by your prompt content. YAML front-matter is also supported as an alternative.

TOML (default):

---
title = "My Prompt"
version = "1.0.0"
author = "Your Name"
description = "What this prompt does"
created = "2024-01-15"
tags = ["customer-support", "greeting"]
---
Your prompt content goes here.

Use {variables} for templating.

YAML alternative:

---
title: "My Prompt"
version: "1.0.0"
author: "Your Name"
description: "What this prompt does"
created: "2024-01-15"
tags:
  - customer-support
  - greeting
---
Your prompt content goes here.

Use {variables} for templating.

Both formats are auto-detected based on the front-matter content.

Metadata Modes

Choose the right level of strictness for your use case:

  1. ALLOW (default) - Load metadata if present, don't worry about completeness
  2. IGNORE - Simple text file loading, filename becomes title
  3. STRICT - Require complete metadata (title, description, version) for production safety

You can also set the environment variable TEXTPROMPTS_METADATA_MODE to one of strict, allow, or ignore before importing the library to configure the default mode.

# Set globally
textprompts.set_metadata("allow")    # Default: load metadata when available
textprompts.set_metadata("ignore")   # Simple: no metadata parsing
textprompts.set_metadata("strict")   # Production: require complete metadata

# Or override per prompt
prompt = textprompts.load_prompt("file.txt", meta="strict")

API Reference

load_prompt(path, *, meta=None)

Load a single prompt file.

  • path: Path to the prompt file
  • meta: Metadata handling mode - MetadataMode.STRICT, MetadataMode.ALLOW, MetadataMode.IGNORE, or string equivalents. None uses global config.

Returns a Prompt object with:

  • prompt.meta: Metadata from TOML/YAML front-matter (always present)
  • prompt.prompt: The prompt content as a PromptString
  • prompt.path: Path to the original file

set_metadata(mode) / get_metadata()

Set or get the global metadata handling mode.

  • mode: MetadataMode.STRICT, MetadataMode.ALLOW, MetadataMode.IGNORE, or string equivalents
import textprompts

# Set global mode
textprompts.set_metadata(textprompts.MetadataMode.STRICT)
textprompts.set_metadata("allow")  # String also works

# Get current mode
current_mode = textprompts.get_metadata()

save_prompt(path, content, *, format="toml")

Save a prompt to a file.

  • path: Path to save the prompt file
  • content: Either a string (creates template with required fields) or a Prompt object
  • format: Front-matter format to use - "toml" (default) or "yaml"
from textprompts import save_prompt

# Save a simple prompt with metadata template
save_prompt("my_prompt.txt", "You are a helpful assistant.")

# Save with YAML front-matter
save_prompt("my_prompt.txt", "You are a helpful assistant.", format="yaml")

# Save a Prompt object with full metadata
save_prompt("my_prompt.txt", prompt_object)

parse_sections(text) and section utilities

Parse mixed Markdown/XML prompt structure without going through the file loader.

  • parse_sections(text): Returns a ParseResult with sections, anchors, duplicate_anchors, frontmatter, and total_chars
  • generate_slug(heading): Creates the same auto-anchor slug used by the parser (lowercase, non-alphanumeric runs -> _)
  • inject_anchors(text): Inserts missing <a id="..."></a> lines before Markdown headings and returns (text, result)
  • render_toc(result, path): Renders a human-readable table of contents
from textprompts import inject_anchors, parse_sections, render_toc

result = parse_sections("## Intro\n\nBody.")
print(result.sections[0].anchor_id)  # "intro"

anchored_text, anchored = inject_anchors("## Intro\n\nBody.")
print(anchored_text)  # <a id="intro"></a>\n## Intro...

print(render_toc(anchored, "prompt.txt"))

Anchor IDs use a canonical underscore form. For example, generate_slug("My Section") returns my_section, id="my-section" normalizes to my_section, and generic XML sections default to the normalized tag name when no explicit id is present.

PromptString

A string subclass that validates format() calls:

from textprompts import PromptString

template = PromptString("Hello {name}, you are {role}")

# Strict formatting (default) - all placeholders required
result = template.format(name="Alice", role="admin")  # ✅ Works
result = template.format(name="Alice")  # ❌ Raises ValueError

# Partial formatting - replace only available placeholders
partial = template.format(name="Alice", skip_validation=True)  # ✅ "Hello Alice, you are {role}"

# Access placeholder information
print(template.placeholders)  # {'name', 'role'}

Error Handling

TextPrompts provides specific exception types:

from textprompts import (
    TextPromptsError,       # Base exception
    FileMissingError,       # File not found
    MissingMetadataError,   # No TOML front-matter when required
    InvalidMetadataError,   # Invalid TOML syntax
    MalformedHeaderError,   # Malformed front-matter structure
    MetadataMode,           # Metadata handling mode enum
    set_metadata,           # Set global metadata mode
    get_metadata            # Get global metadata mode
)

CLI Tool

TextPrompts includes a CLI for quick prompt inspection:

# View a single prompt
textprompts show greeting.txt

# List all prompts in a directory
textprompts list prompts/ --recursive

# Validate prompts
textprompts validate prompts/

Best Practices

  1. Organize by purpose: Group related prompts in folders

    prompts/
    ├── customer-support/
    ├── content-generation/
    └── code-review/
    
  2. Use semantic versioning: Version your prompts like code

    version = "1.2.0"  # major.minor.patch
    
  3. Document your variables: List expected variables in descriptions

    description = "Requires: customer_name, issue_type, agent_name"
    
  4. Test your prompts: Write unit tests for critical prompts

    def test_greeting_prompt():
     prompt = load_prompt("greeting.txt")
     result = prompt.prompt.format(customer_name="Test")
        assert "Test" in result
    
  5. Use environment-specific prompts: Different prompts for dev/prod

    env = os.getenv("ENV", "development")
    prompt = load_prompt(f"prompts/{env}/system.txt")
    

Why Not Just Use String Templates?

You could, but then you lose:

  • Metadata tracking (versions, authors, descriptions)
  • Safe formatting (catch missing variables)
  • Organized storage (searchable, documentable)
  • Version control benefits (proper diffs, blame, history)
  • Tooling support (CLI, validation, testing)

Contributing

We welcome contributions! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE for details.


textprompts - Because your prompts deserve better than being buried in code strings. 🚀

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

textprompts-2.0.0.tar.gz (47.5 kB view details)

Uploaded Source

Built Distribution

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

textprompts-2.0.0-py3-none-any.whl (58.4 kB view details)

Uploaded Python 3

File details

Details for the file textprompts-2.0.0.tar.gz.

File metadata

  • Download URL: textprompts-2.0.0.tar.gz
  • Upload date:
  • Size: 47.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for textprompts-2.0.0.tar.gz
Algorithm Hash digest
SHA256 01e067cdf51b8ca92bfd11967f5ebf89e12f3abab1e3fc0543bf3e63fefbf080
MD5 6405b88ceeea06ae0d0647f50f6c913f
BLAKE2b-256 ff3d41887dbe2ca3353f8d48d9aa241bacd95fa2e1dee6b95bd445dd9b0940b7

See more details on using hashes here.

File details

Details for the file textprompts-2.0.0-py3-none-any.whl.

File metadata

  • Download URL: textprompts-2.0.0-py3-none-any.whl
  • Upload date:
  • Size: 58.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.11 {"installer":{"name":"uv","version":"0.10.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for textprompts-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 721e4899ecf9a4140f4ae694af8883395b17889eb01d469660d57b0f156c0723
MD5 736d1447e683ba9e1f87baa2f1e2113a
BLAKE2b-256 0320784b7dbe4774bcc755204a5409b35e6a7b99379bde2347d406ae63e2a601

See more details on using hashes here.

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