# Prompt Template
A lightweight, zero-dependency Python library for managing LLM prompt templates. Built on the design principles of
`string.Template` but with enhanced features specifically designed for LLM prompt engineering.
## Features
- Strong template validation with detailed error messages
- Support for nested braces and complex JSON structures
- Automatic value serialization for common Python types
- Default value support with deep copying for mutable types
- Incremental template population with value inheritance
- Type hints for enhanced IDE support
- Zero dependencies - just pure Python
- 100% test coverage
## Installation
```bash
pip install prompt-template
```
## Usage
The library is intentionally very simple to use.
The idea is to keep it simple, have validation and serialization builtin, and make debugging simple.
### Simple Templates
```python
from prompt_template import PromptTemplate
# Create a template
template = PromptTemplate("Hello ${name}! Welcome to ${location}.")
# Render with values
result = template.to_string(name="Alice", location="Wonderland")
print(result) # Hello Alice! Welcome to Wonderland.
```
### Default Values
```python
# Set default values that can be overridden later
template = PromptTemplate("Hello ${name}! Your settings are: ${settings}")
# Set default values - they're safely deep copied
template.set_default(
name="Guest",
settings={"theme": "light", "language": "en"}
)
# Use with defaults
print(template.to_string())
# Hello Guest! Your settings are: {"theme": "light", "language": "en"}
# Override specific values
print(template.to_string(name="Alice"))
# Hello Alice! Your settings are: {"theme": "light", "language": "en"}
# Override everything
print(template.to_string(
name="Bob",
settings={"theme": "dark", "language": "fr"}
))
# Hello Bob! Your settings are: {"theme": "dark", "language": "fr"}
```
### Named Templates
Adding a name to your template enhances error messages with context. Templates with the same name and content are considered equal:
```python
# Create named templates
template1 = PromptTemplate(
name="user_greeting",
template="Hello ${name}! Welcome to ${location}."
)
# Templates can be compared and used as dictionary keys
template2 = PromptTemplate(
name="user_greeting",
template="Hello ${name}! Welcome to ${location}."
)
assert template1 == template2
# Templates have a readable string representation
print(template1) # PromptTemplate [user_greeting]:\n\nHello ${name}! Welcome to ${location}.
```
### Complex JSON Templates
The library handles nested structures elegantly:
```python
template = PromptTemplate("""
{
"user": {
"name": "${username}",
"role": "${role}"
},
"settings": {
"theme": "${theme}",
"notifications": ${notifications},
"preferences": ${preferences}
}
}
""")
# Values are automatically serialized
result = template.to_string(
username="john_doe",
role="admin",
theme="dark",
notifications={"email": True, "push": False},
preferences=["daily_digest", "weekly_report"]
)
```
### Template Methods
The library provides two main methods for populating templates:
```python
# to_string(): Validates inputs, uses defaults, and serializes values
template = PromptTemplate("Hello ${name}!")
template.set_default(name="Guest")
result = template.to_string() # Uses default
result = template.to_string(name="Alice") # Overrides default
# substitute(): Lower-level method for partial population
base = PromptTemplate("${greeting} ${name}!")
partial = base.substitute(greeting="Hello") # Returns a string
final = PromptTemplate(partial).to_string(name="Alice")
```
### Nested Templates
Templates can be nested within other templates:
```python
# Create nested templates
inner = PromptTemplate("My name is ${name}")
inner.set_default(name="Alice")
outer = PromptTemplate("""
User message:
${message}
""")
# Combine templates
result = outer.to_string(message=inner.to_string())
print(result)
# User message:
# My name is Alice
```
### Type Handling and Automatic Serialization
The library automatically handles various Python types:
```python
from uuid import UUID
from decimal import Decimal
from datetime import datetime
template = PromptTemplate("""
{
"id": "${id}",
"amount": "${amount}",
"metadata": ${metadata}
}
""")
result = template.to_string(
id=UUID("550e8400-e29b-41d4-a716-446655440000"),
amount=Decimal("45.67"),
metadata={
"timestamp": datetime.now(), # Serialized via JSON
"values": [1, 2, 3]
}
)
```
### Custom Serialization
Extend the base class to customize value serialization with proper error handling:
```python
from typing import Any
from datetime import datetime
from decimal import Decimal
from prompt_template import PromptTemplate as BasePromptTemplate
import orjson
class PromptTemplate(BasePromptTemplate):
@staticmethod
def serializer(value: Any) -> str:
"""Custom serializer with orjson for better performance.
Handles special cases and provides detailed error messages.
"""
try:
# Handle special types first
if isinstance(value, (datetime, Decimal)):
return str(value)
# Use orjson for everything else
return orjson.dumps(value).decode('utf-8')
except (TypeError, ValueError) as e:
raise TypeError(
f"Failed to serialize value of type {type(value).__name__}: {e}"
) from e
```
The custom serializer will be used automatically:
```python
template = PromptTemplate("""
{
"timestamp": "${time}",
"amount": "${price}",
"data": ${complex_data}
}
""")
result = template.to_string(
time=datetime.now(),
price=Decimal("45.67"),
complex_data={"nested": [1, 2, 3]}
)
```
## Error Handling
The library provides specific extensive errors:
### Missing Values
```python
from prompt_template import MissingTemplateValuesError
template = PromptTemplate("Hello ${name}!")
try:
template.to_string() # No values provided
except MissingTemplateValuesError as e:
print(f"Missing values: {e.missing_values}") # {'name'}
```
### Invalid Keys
```python
from prompt_template import InvalidTemplateKeysError
template = PromptTemplate("Hello ${name}!")
try:
template.to_string(name="World", invalid_key="value")
except InvalidTemplateKeysError as e:
print(f"Invalid keys: {e.invalid_keys}") # ['invalid_key']
print(f"Valid keys: {e.valid_keys}") # {'name'}
```
### Serialization Errors
```python
from prompt_template import TemplateSerializationError
template = PromptTemplate("Value: ${value}")
try:
template.to_string(value=object()) # Non-serializable object
except TemplateSerializationError as e:
print(f"Failed to serialize key '{e.key}': {e.original_error}")
```
## License
MIT License
Raw data
{
"_id": null,
"home_page": null,
"name": "prompt-template",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "ai, llm, prompt-engineering, prompt-template, templating",
"author": null,
"author_email": "Na'aman Hirschfeld <nhirschfed@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/bc/3b/6918eeede245be7b22bcbb3419d85aadb2e9d7fd201c2384389ca1dc8dc8/prompt_template-1.2.0.tar.gz",
"platform": null,
"description": "# Prompt Template\n\nA lightweight, zero-dependency Python library for managing LLM prompt templates. Built on the design principles of\n`string.Template` but with enhanced features specifically designed for LLM prompt engineering.\n\n## Features\n\n- Strong template validation with detailed error messages\n- Support for nested braces and complex JSON structures\n- Automatic value serialization for common Python types\n- Default value support with deep copying for mutable types\n- Incremental template population with value inheritance\n- Type hints for enhanced IDE support\n- Zero dependencies - just pure Python\n- 100% test coverage\n\n## Installation\n\n```bash\npip install prompt-template\n```\n\n## Usage\n\nThe library is intentionally very simple to use.\nThe idea is to keep it simple, have validation and serialization builtin, and make debugging simple.\n\n### Simple Templates\n\n```python\nfrom prompt_template import PromptTemplate\n\n# Create a template\ntemplate = PromptTemplate(\"Hello ${name}! Welcome to ${location}.\")\n\n# Render with values\nresult = template.to_string(name=\"Alice\", location=\"Wonderland\")\nprint(result) # Hello Alice! Welcome to Wonderland.\n```\n\n### Default Values\n\n```python\n# Set default values that can be overridden later\ntemplate = PromptTemplate(\"Hello ${name}! Your settings are: ${settings}\")\n\n# Set default values - they're safely deep copied\ntemplate.set_default(\n name=\"Guest\",\n settings={\"theme\": \"light\", \"language\": \"en\"}\n)\n\n# Use with defaults\nprint(template.to_string())\n# Hello Guest! Your settings are: {\"theme\": \"light\", \"language\": \"en\"}\n\n# Override specific values\nprint(template.to_string(name=\"Alice\"))\n# Hello Alice! Your settings are: {\"theme\": \"light\", \"language\": \"en\"}\n\n# Override everything\nprint(template.to_string(\n name=\"Bob\",\n settings={\"theme\": \"dark\", \"language\": \"fr\"}\n))\n# Hello Bob! Your settings are: {\"theme\": \"dark\", \"language\": \"fr\"}\n```\n\n### Named Templates\n\nAdding a name to your template enhances error messages with context. Templates with the same name and content are considered equal:\n\n```python\n# Create named templates\ntemplate1 = PromptTemplate(\n name=\"user_greeting\",\n template=\"Hello ${name}! Welcome to ${location}.\"\n)\n\n# Templates can be compared and used as dictionary keys\ntemplate2 = PromptTemplate(\n name=\"user_greeting\",\n template=\"Hello ${name}! Welcome to ${location}.\"\n)\nassert template1 == template2\n\n# Templates have a readable string representation\nprint(template1) # PromptTemplate [user_greeting]:\\n\\nHello ${name}! Welcome to ${location}.\n```\n\n### Complex JSON Templates\n\nThe library handles nested structures elegantly:\n\n```python\ntemplate = PromptTemplate(\"\"\"\n{\n \"user\": {\n \"name\": \"${username}\",\n \"role\": \"${role}\"\n },\n \"settings\": {\n \"theme\": \"${theme}\",\n \"notifications\": ${notifications},\n \"preferences\": ${preferences}\n }\n}\n\"\"\")\n\n# Values are automatically serialized\nresult = template.to_string(\n username=\"john_doe\",\n role=\"admin\",\n theme=\"dark\",\n notifications={\"email\": True, \"push\": False},\n preferences=[\"daily_digest\", \"weekly_report\"]\n)\n```\n\n### Template Methods\n\nThe library provides two main methods for populating templates:\n\n```python\n# to_string(): Validates inputs, uses defaults, and serializes values\ntemplate = PromptTemplate(\"Hello ${name}!\")\ntemplate.set_default(name=\"Guest\")\nresult = template.to_string() # Uses default\nresult = template.to_string(name=\"Alice\") # Overrides default\n\n# substitute(): Lower-level method for partial population\nbase = PromptTemplate(\"${greeting} ${name}!\")\npartial = base.substitute(greeting=\"Hello\") # Returns a string\nfinal = PromptTemplate(partial).to_string(name=\"Alice\")\n```\n\n### Nested Templates\n\nTemplates can be nested within other templates:\n\n```python\n# Create nested templates\ninner = PromptTemplate(\"My name is ${name}\")\ninner.set_default(name=\"Alice\")\n\nouter = PromptTemplate(\"\"\"\nUser message:\n${message}\n\"\"\")\n\n# Combine templates\nresult = outer.to_string(message=inner.to_string())\nprint(result)\n# User message:\n# My name is Alice\n```\n\n### Type Handling and Automatic Serialization\n\nThe library automatically handles various Python types:\n\n```python\nfrom uuid import UUID\nfrom decimal import Decimal\nfrom datetime import datetime\n\ntemplate = PromptTemplate(\"\"\"\n{\n \"id\": \"${id}\",\n \"amount\": \"${amount}\",\n \"metadata\": ${metadata}\n}\n\"\"\")\n\nresult = template.to_string(\n id=UUID(\"550e8400-e29b-41d4-a716-446655440000\"),\n amount=Decimal(\"45.67\"),\n metadata={\n \"timestamp\": datetime.now(), # Serialized via JSON\n \"values\": [1, 2, 3]\n }\n)\n```\n\n### Custom Serialization\n\nExtend the base class to customize value serialization with proper error handling:\n\n```python\nfrom typing import Any\nfrom datetime import datetime\nfrom decimal import Decimal\nfrom prompt_template import PromptTemplate as BasePromptTemplate\nimport orjson\n\n\nclass PromptTemplate(BasePromptTemplate):\n @staticmethod\n def serializer(value: Any) -> str:\n \"\"\"Custom serializer with orjson for better performance.\n\n Handles special cases and provides detailed error messages.\n \"\"\"\n try:\n # Handle special types first\n if isinstance(value, (datetime, Decimal)):\n return str(value)\n\n # Use orjson for everything else\n return orjson.dumps(value).decode('utf-8')\n except (TypeError, ValueError) as e:\n raise TypeError(\n f\"Failed to serialize value of type {type(value).__name__}: {e}\"\n ) from e\n```\n\nThe custom serializer will be used automatically:\n\n```python\ntemplate = PromptTemplate(\"\"\"\n{\n \"timestamp\": \"${time}\",\n \"amount\": \"${price}\",\n \"data\": ${complex_data}\n}\n\"\"\")\n\nresult = template.to_string(\n time=datetime.now(),\n price=Decimal(\"45.67\"),\n complex_data={\"nested\": [1, 2, 3]}\n)\n```\n\n## Error Handling\n\nThe library provides specific extensive errors:\n\n### Missing Values\n\n```python\nfrom prompt_template import MissingTemplateValuesError\n\ntemplate = PromptTemplate(\"Hello ${name}!\")\ntry:\n template.to_string() # No values provided\nexcept MissingTemplateValuesError as e:\n print(f\"Missing values: {e.missing_values}\") # {'name'}\n```\n\n### Invalid Keys\n\n```python\nfrom prompt_template import InvalidTemplateKeysError\n\ntemplate = PromptTemplate(\"Hello ${name}!\")\ntry:\n template.to_string(name=\"World\", invalid_key=\"value\")\nexcept InvalidTemplateKeysError as e:\n print(f\"Invalid keys: {e.invalid_keys}\") # ['invalid_key']\n print(f\"Valid keys: {e.valid_keys}\") # {'name'}\n```\n\n### Serialization Errors\n\n```python\nfrom prompt_template import TemplateSerializationError\n\ntemplate = PromptTemplate(\"Value: ${value}\")\ntry:\n template.to_string(value=object()) # Non-serializable object\nexcept TemplateSerializationError as e:\n print(f\"Failed to serialize key '{e.key}': {e.original_error}\")\n```\n\n## License\n\nMIT License\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A robust zero-dependency library for creating prompt templates",
"version": "1.2.0",
"project_urls": {
"homepage": "https://github.com/Goldziher/prompt-template"
},
"split_keywords": [
"ai",
" llm",
" prompt-engineering",
" prompt-template",
" templating"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "34d0ed203aabbb1d131fbea89c5fe42aa13cadb49ebc5dafc781170049e3c7f5",
"md5": "25d1aa797f7c77ae108ea3e3bc01ab2d",
"sha256": "96f6f0f8d3fcbddd0816aeb2cd95632b9e08d38e786963a23260abf6da45629b"
},
"downloads": -1,
"filename": "prompt_template-1.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "25d1aa797f7c77ae108ea3e3bc01ab2d",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 8733,
"upload_time": "2025-02-12T18:55:43",
"upload_time_iso_8601": "2025-02-12T18:55:43.955968Z",
"url": "https://files.pythonhosted.org/packages/34/d0/ed203aabbb1d131fbea89c5fe42aa13cadb49ebc5dafc781170049e3c7f5/prompt_template-1.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "bc3b6918eeede245be7b22bcbb3419d85aadb2e9d7fd201c2384389ca1dc8dc8",
"md5": "1227357bda238b022ecb40d185ded25d",
"sha256": "03c2099b83080d144448e12c7f94192e3503cfa5c55359f2e07a6143703d97c2"
},
"downloads": -1,
"filename": "prompt_template-1.2.0.tar.gz",
"has_sig": false,
"md5_digest": "1227357bda238b022ecb40d185ded25d",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 39087,
"upload_time": "2025-02-12T18:55:45",
"upload_time_iso_8601": "2025-02-12T18:55:45.870208Z",
"url": "https://files.pythonhosted.org/packages/bc/3b/6918eeede245be7b22bcbb3419d85aadb2e9d7fd201c2384389ca1dc8dc8/prompt_template-1.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-02-12 18:55:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "Goldziher",
"github_project": "prompt-template",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "prompt-template"
}