Getting started — Python
Install
Section titled “Install”pip install prompting-pressuv add prompting-presspoetry add prompting-presspdm add prompting-pressThe PyPI distribution name is prompting-press; the import name is prompting_press. Python 3.12+
required (the wheel is abi3-py312 — one wheel covers CPython 3.12 and up).
Write a prompt definition
Section titled “Write a prompt definition”A prompt definition is YAML, JSON, or TOML text — the same shape in any of the three formats. The
caller reads the text and hands it to the from_* factories; the library does no file I/O. The
assistant definition used throughout this page:
name: assistantrole: systembody: "You are a support assistant for {{ company }}. Keep your replies under {{ max_words }} words."variables: company: type: string trusted: true max_words: type: integer trusted: true{ "name": "assistant", "role": "system", "body": "You are a support assistant for {{ company }}. Keep your replies under {{ max_words }} words.", "variables": { "company": { "type": "string", "trusted": true }, "max_words": { "type": "integer", "trusted": true } }}name = "assistant"role = "system"body = "You are a support assistant for {{ company }}. Keep your replies under {{ max_words }} words."
[variables.company]type = "string"trusted = true
[variables.max_words]type = "integer"trusted = trueConstruct and render
Section titled “Construct and render”Step 1 — construct a Prompt (and validate)
Section titled “Step 1 — construct a Prompt (and validate)”Read the definition file and hand its text to a from_* factory (from_yaml / from_json /
from_toml), or build from a shape object with Prompt(...) — either a typed PromptDefinition or
a plain dict. The library does no file I/O — the caller reads the file. Every route runs the
same validation immediately:
"""Construct a Prompt from a definition file with a from_* factory (validates immediately)."""
from pathlib import Path
from prompting_press import Prompt
# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parent
assistant = Prompt.from_yaml( (_HERE / "assistant.yaml").read_text()) # validates here, or raises# The same definition in JSON or TOML parses into an identical Prompt:# assistant = Prompt.from_json((_HERE / "assistant.json").read_text())# assistant = Prompt.from_toml((_HERE / "assistant.toml").read_text())
assert assistant.name == "assistant"assert ( assistant.body == "You are a support assistant for {{ company }}. Keep your replies under {{ max_words }} words.")"""Construct a Prompt from a typed PromptDefinition shape (same validation as from_*)."""
from prompting_press import Prompt, PromptDefinition
# PromptDefinition is the generated Pydantic shape — an editor/type-checker# checks the fields, the role enum, and each variable's `trusted` flag at author time.definition = PromptDefinition( name="assistant", role="system", body="You are a support assistant for {{ company }}. Keep your replies under {{ max_words }} words.", variables={ "company": {"type": "string", "trusted": True}, "max_words": {"type": "integer", "trusted": True}, },)
assistant = Prompt(definition) # same validation as the from_* factoriesassert assistant.name == "assistant""""Construct a Prompt from a plain dict (the Rust loader validates the shape on the way in)."""
from prompting_press import Prompt
# A plain dict works too — convenient when the shape comes from already-parsed# config. The Rust loader validates the shape on the way in, the same as the typed form.assistant = Prompt( { "name": "assistant", "role": "system", "body": "You are a support assistant for {{ company }}. Keep your replies under {{ max_words }} words.", "variables": { "company": {"type": "string", "trusted": True}, "max_words": {"type": "integer", "trusted": True}, }, })
assert assistant.name == "assistant"Construction validates immediately: if the body had referenced {{ industry }} — a variable
variables doesn’t declare — construction would have raised PromptRenderError (code
undefined_variable), before any render (a malformed document raises LoadError). A
template/variable disagreement is caught at construction, never as a silent empty render.
Step 2 — declare the typed Vars model
Section titled “Step 2 — declare the typed Vars model”The render values are a caller-owned Pydantic model whose fields match the prompt’s variables
(company, max_words). This model is the render-time gate: you hand it to render in Step 3,
and its validators (here, max_words must be a positive integer) decide what data is allowed to
reach the kernel. Declaring it is just the definition — the enforcement happens at render:
"""Declare the typed Vars model — a caller-owned Pydantic model whose validatorsrun before the kernel is touched."""
from pydantic import BaseModel, field_validator
class AssistantVars(BaseModel): company: str max_words: int
@field_validator("max_words") @classmethod def _at_least_one(cls, v: int) -> int: if v < 1: raise ValueError("max_words must be at least 1") return v
# The model matches the prompt's `variables` (company, max_words) and its validators run first.ok = AssistantVars(company="Acme Robotics", max_words=50)assert ok.company == "Acme Robotics"assert ok.max_words == 50
# A max_words below 1 is rejected by the field validator.try: AssistantVars(company="Acme Robotics", max_words=0) raise AssertionError("expected a validation error for max_words below 1")except ValueError as exc: assert "max_words must be at least 1" in str(exc)Step 3 — render (the model validates the data), and read the result
Section titled “Step 3 — render (the model validates the data), and read the result”Hand the Prompt from Step 1 the AssistantVars from Step 2 — pass the class plus a data dict.
The model runs first — valid data renders; invalid data (e.g. max_words: 0) raises
PromptValidationError and the kernel is never touched. The sample shows both the passing render
and the rejection:
"""Render, and read the result — hand the Prompt the Vars class plus a data dict;it validates + renders in one call, returning a RenderResult. The same Vars classalso rejects bad data at render, before the kernel ever sees it."""
import refrom pathlib import Path
from prompting_press import Prompt, PromptValidationErrorfrom pydantic import BaseModel, field_validator
class AssistantVars(BaseModel): company: str max_words: int
@field_validator("max_words") @classmethod def _at_least_one(cls, v: int) -> int: if v < 1: raise ValueError("max_words must be at least 1") return v
# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parentassistant = Prompt.from_yaml((_HERE / "assistant.yaml").read_text())
result = assistant.render( AssistantVars, data={"company": "Acme Robotics", "max_words": 50})
assert ( result.text == "You are a support assistant for Acme Robotics. Keep your replies under 50 words.")assert result.variant == "default" # same arm assistant.body showed in Step 1assert re.fullmatch( r"[0-9a-f]{64}", result.template_hash) # 64-char lowercase-hex SHA-256 of the templateassert re.fullmatch( r"[0-9a-f]{64}", result.render_hash) # 64-char lowercase-hex SHA-256 of result.textassert result.guard is None # no guard requested
# The same AssistantVars validates at render: bad data is rejected before the kernel.try: assistant.render(AssistantVars, data={"company": "Acme Robotics", "max_words": 0}) raise AssertionError("expected a validation error for max_words below 1")except PromptValidationError as exc: row = exc.errors[0] assert row.field == "max_words" assert row.code == "validation" assert "max_words must be at least 1" in row.messagerender(model=None, *, data=None, variant=None, guard=None) — data/variant/guard are
keyword-only:
- Pass a Pydantic class +
data=dict (as above), or a pre-built instance asmodelwith nodata. - The class-plus-
dataform is the one to reach for by default — it is what makes the typed-vars validation above actually run. variant=Noneselects the default (root body) arm;guard=Noneopts out of the advisory guard.
Complete example
Section titled “Complete example”"""Complete example — construct (validates), then render with typed, Pydantic-validated vars."""
import refrom pathlib import Path
from prompting_press import Promptfrom pydantic import BaseModel, field_validator
class AssistantVars(BaseModel): company: str max_words: int
@field_validator("max_words") @classmethod def _at_least_one(cls, v: int) -> int: if v < 1: raise ValueError("max_words must be at least 1") return v
# 1. Construct (validates here).# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parentassistant = Prompt.from_yaml((_HERE / "assistant.yaml").read_text())
# 2 + 3. Render with the typed, Pydantic-validated vars.result = assistant.render( AssistantVars, data={"company": "Acme Robotics", "max_words": 50})print( result.text) # You are a support assistant for Acme Robotics. Keep your replies under 50 words.print(result.template_hash) # 64-char hex
assert ( result.text == "You are a support assistant for Acme Robotics. Keep your replies under 50 words.")assert re.fullmatch(r"[0-9a-f]{64}", result.template_hash)What RenderResult carries
Section titled “What RenderResult carries”| Field | Value |
|---|---|
text | The rendered body text. |
name | The prompt’s name. |
variant | The resolved variant name ("default" when none was selected). |
template_hash | SHA256(resolved variant template source) — lowercase hex. |
render_hash | SHA256(rendered output text) — lowercase hex. |
guard | `str |
Error types
Section titled “Error types”"""Error types — a rejected render value raises PromptValidationError, whose`.errors` are normalized FieldError rows (.field, .code, .message)."""
from pathlib import Path
from prompting_press import ( Prompt, PromptingPressError, PromptValidationError, PromptRenderError, LoadError,)from pydantic import BaseModel, field_validator
class AssistantVars(BaseModel): company: str max_words: int
@field_validator("max_words") @classmethod def _at_least_one(cls, v: int) -> int: if v < 1: raise ValueError("max_words must be at least 1") return v
# The caller reads the definition; the library does no file I/O itself.# Resolve the file next to this program (a real app uses its own path)._HERE = Path(__file__).parentassistant = Prompt.from_yaml((_HERE / "assistant.yaml").read_text())
# The specific exceptions all derive from PromptingPressError.assert issubclass(PromptValidationError, PromptingPressError)assert issubclass(PromptRenderError, PromptingPressError)assert issubclass(LoadError, PromptingPressError)
try: result = assistant.render( AssistantVars, data={"company": "Acme Robotics", "max_words": 0} ) raise AssertionError("expected a validation error for max_words below 1")except PromptValidationError as exc: for row in exc.errors: print(row.field, row.code, row.message) # "max_words" "validation" "max_words must be at least 1" row = exc.errors[0] assert row.field == "max_words" assert row.code == "validation" assert "max_words must be at least 1" in row.messageThe exception hierarchy:
PromptingPressError # base; carries .errors -> list[FieldError]├── PromptValidationError # Pydantic validation failed (code "validation")├── PromptRenderError # kernel render failure (codes: unknown_variant,│ # undefined_variable, parse, render, excluded_feature)└── LoadError # malformed YAML/JSON/TOML (code "load")Each FieldError carries .field, .code, and .message. A rejected render value is never
echoed onto the error surface; a parse error carries template-syntax detail (no bound values).
Next steps
Section titled “Next steps”- Variants — declare alternative bodies and select one at render.
- Deriving a prompt — produce a changed copy of an immutable prompt.
- Metadata — attach and read back opaque prompt-level and per-variant metadata.
- Compose multi-message prompts — aggregate
Promptobjects into a role-tagged sequence. - The advisory guard — tag untrusted variables and surface the advisory guard text.
- Lint prompts in CI — wire
prompt.check()as a test that fails the build. - Template features — which MiniJinja features are supported (and which are deliberately excluded).
- Python API reference — full method signatures.
docs current as of 0.2.0