Python (sufleur-cli)
The pip wrapper around the Sufleur CLI and the Python code it generates.
The sufleur-cli package on PyPI wraps the Sufleur CLI for Python projects. It bundles the platform-appropriate Go binary inside the wheel and provides the typed runtime the generated code depends on.
The PyPI listing: pypi.org/project/sufleur-cli.
Install
uv add --dev sufleur-cli
# or: pip install sufleur-cli
# or: poetry add --group dev sufleur-cliThe wheel is built by GoReleaser per platform — installing on macOS arm64 pulls a wheel containing the macOS arm64 binary, on Linux x86_64 you get the Linux x86_64 binary, and so on. There's no separate download step at install time.
Peer dependencies
The generated code imports two libraries; install them in your project:
uv add chevron pydantic
# or: pip install chevron pydanticchevron is the runtime Mustache implementation (chosen for full-spec compliance). pydantic powers the output-schema models. Both are intentionally peer deps.
Running the CLI
The package installs a sufleur console script:
sufleur init
sufleur add @acme/welcome-message
sufleur generateYou can also invoke it as a module — useful in venvs where the script shim isn't on PATH:
python -m sufleur_cli generateSee the CLI reference for every command and flag.
What the generated code looks like
sufleur generate writes a single file (default: ./generated/prompts.py) with TypedDicts for inputs, Pydantic BaseModels for outputs, and a typed get_prompt factory. Excerpt:
from __future__ import annotations
from typing import Any, Literal, TypedDict
import chevron
import json
from pydantic import BaseModel
class _WelcomeMessage_UserPromptInput_User(TypedDict):
age: int
"""User's age in years"""
name: str
"""User's name"""
class WelcomeMessage_UserPromptInput(TypedDict):
isWeatherGood: bool
user: _WelcomeMessage_UserPromptInput_User
class WelcomeMessageOutput(BaseModel):
email: str
id: int
name: str
PromptName = Literal["@acme/welcome-message"]
def get_prompt(prompt_name: PromptName): ...A few things to note:
- Nested objects become their own TypedDict (with the
_prefix marking them as internal).@docannotations land as field docstrings. - Output schemas become Pydantic
BaseModels — fully validated, with.model_dump()and friends available out of the box. - The
Literaltype forPromptNamemeans your editor autocompletes prompt names.
Calling a prompt
from generated.prompts import get_prompt
welcome = get_prompt('@acme/welcome-message')
result = welcome.render('userPrompt', {
'isWeatherGood': True,
'user': {'name': 'Ada', 'age': 32},
})
# `result['prompt']` is the rendered string.
print(result['prompt'])
# `welcome.metadata` exposes model, temperature, version, and (if set) the raw output_schema.
print(welcome.metadata['version']) # "1.2.3"If a prompt declares an outputSchema, the returned object exposes parse_output:
result = welcome.parse_output(llm_response_text)
if result['success']:
user = result['data'] # typed as WelcomeMessageOutput
print(user.email)
else:
print(result['error'])parse_output strips a fenced ```code block if the model wrapped its JSON, then validates against the Pydantic model. The return is always a TypedDict union — no exception path to catch.
Draft warnings
Pinning to a draft version is supported but produces a runtime warning the first time you call the prompt:
UserWarning: [sufleur] Prompt "@acme/welcome-message" is a draft version
Drafts are mutable in the registry, so they're appropriate for local iteration — not for production.
Versioning the generated file
Commit generated/prompts.py to your repo alongside sufleur.yaml and sufleur-lock.yaml. The trio is what makes prompt changes reviewable in PRs — reviewers see the new template, the version bump, and the calling code in the same diff.